ในบทแนะนำต่อไปนี้ เราจะสาธิตวิธีสร้างฟีดใน JSON โดยใช้คำจำกัดความของ Google Protocol Buffers (protobuf) เราจะใช้คอมไพเลอร์ Protobuf เพื่อสร้างซอร์สโค้ดตามสคีมา Protobuf การสร้างฟีดโดยใช้ซอร์สโค้ดที่สร้างขึ้นจะช่วยให้มีอินเทอร์เฟซที่ใช้งานง่าย และป้องกันไม่ให้มีการสร้างเอนทิตีฟีดที่มีชื่อฟิลด์หรือประเภทฟิลด์ที่ไม่ถูกต้อง
การตั้งค่าโปรเจ็กต์
- สร้างไดเรกทอรีโปรเจ็กต์ใหม่
- คัดลอกเนื้อหาของ offer.proto money.proto dayofweek.proto timeofday.proto date.proto จากคำจำกัดความของ Proto เป็นไฟล์ใหม่ไปยังรูทของไดเรกทอรีโปรเจ็กต์
- ติดตั้งคอมไพเลอร์ Protoc
หากต้องการสร้างซอร์สโค้ดจากไฟล์ .proto คุณจะต้องมีคอมไพเลอร์ protoc ดาวน์โหลดไบนารีที่สร้างไว้ล่วงหน้าล่าสุดจากหน้าการเผยแพร่ Protocol Buffers ใน GitHub
แตกไฟล์ ZIP และเพิ่มเส้นทาง bin ไปยังตัวแปรสภาพแวดล้อม PATH เช่น
unzip protoc-22.0-linux-x86_64.zip -d /usr/local/protoc export PATH="$PATH:/usr/local/protoc/bin"
สร้างซอร์สโค้ด
Python
- ติดตั้งไลบรารี Protobuf ของ Python
pip install protobuf
- สร้างซอร์สโค้ดของไคลเอ็นต์
สร้างไดเรกทอรีเอาต์พุตของ Proto: generated
protoc --python_out=./generated offer.proto money.proto dayofweek.proto timeofday.proto date.proto
อัปเดตตัวแปรสภาพแวดล้อม PYTHONPATH ให้รวมเส้นทางที่สร้างขึ้น เช่น
export PYTHONPATH="$PYTHONPATH:./generated"
หากใช้ Bazel ให้ลองใช้กฎ py_proto_library แทนการเรียกใช้ protoc
ตัวอย่างการใช้งาน
ดูตัวอย่างโปรเจ็กต์ทั้งหมดได้ที่นี่
import json from google.protobuf.json_format import MessageToDict from google.protobuf.timestamp_pb2 import Timestamp # Replace these imports with your actual generated proto package paths from generated import offer_pb2 from generated import money_pb2 from generated import dayofweek_pb2 from generated import timeofday_pb2 _MAX_BYTES_DATA_FILE = 200 * 1024 * 1024 def generate_offer_feed(): # Create OfferFeed feed = offer_pb2.OfferFeed() # Build the offers offer = offer_pb2.Offer( offer_id="offer-1", entity_ids=["dining-1"], offer_source=offer_pb2.OFFER_SOURCE_AGGREGATOR, action_type=offer_pb2.ACTION_TYPE_DINING, offer_modes=[ offer_pb2.OFFER_MODE_WALK_IN, offer_pb2.OFFER_MODE_FREE_RESERVATION ], offer_category=offer_pb2.OFFER_CATEGORY_BASE_OFFER, offer_details=offer_pb2.OfferDetails( offer_display_text="₹100 off on your order", # Note: If this is a 'oneof', you set the field name directly discount_value=money_pb2.Money( currency_code="INR", units=100 ) ), offer_restrictions=offer_pb2.OfferRestrictions( combinable_with_other_offers=True, combinable_offer_categories=[ offer_pb2.OFFER_CATEGORY_ADD_ON_PAYMENT_OFFER, offer_pb2.OFFER_CATEGORY_ADD_ON_COUPON_OFFER ] ), terms=offer_pb2.Terms( restricted_to_certain_users=False, terms_and_conditions="Valid on all menu items." ), validity_periods=[ offer_pb2.ValidityPeriod( valid_period=offer_pb2.ValidityRange( valid_from_time=Timestamp(seconds=1687062000), valid_through_time=Timestamp(seconds=1956556800) ), time_of_day=[ # Monday - Thursday Window offer_pb2.TimeOfDayWindow( time_windows=offer_pb2.TimeOfDayRange( open_time=timeofday_pb2.TimeOfDay(hours=13), close_time=timeofday_pb2.TimeOfDay(hours=23) ), day_of_week=[ dayofweek_pb2.DayOfWeek.MONDAY, dayofweek_pb2.DayOfWeek.TUESDAY, dayofweek_pb2.DayOfWeek.WEDNESDAY, dayofweek_pb2.DayOfWeek.THURSDAY ] ), # Friday - Sunday Window offer_pb2.TimeOfDayWindow( time_windows=offer_pb2.TimeOfDayRange( open_time=timeofday_pb2.TimeOfDay(hours=13), close_time=timeofday_pb2.TimeOfDay(hours=23, minutes=59, seconds=59) ), day_of_week=[ dayofweek_pb2.DayOfWeek.FRIDAY, dayofweek_pb2.DayOfWeek.SATURDAY, dayofweek_pb2.DayOfWeek.SUNDAY ] ) ] ) ], offer_url="https://www.example-restaurant.com/offer/base_offer_1", image_url="https://www.example-restaurant.com/images/offer_base.jpg" ) # Example testing for menu feed size # Protocol buffer message must be less than 2 GiB # https://protobuf.dev/programming-guides/proto-limits/ # It is recommended to not exceed 200 MB, as there is an Actions # Center limit of 200 MB per file after compression. if feed.ByteSize() + offer.ByteSize() < _MAX_BYTES_DATA_FILE: feed.data.append(offer) # else write current feed to file and start a new feed # Serialize to JSON # preserving_proto_field_names=True ensures camelCase becomes snake_case if defined that way in .proto json_output = json.dumps( MessageToDict(feed, preserving_proto_field_name=True) ) print(json_output) if __name__ == "__main__": generate_offer_feed()
ตัวอย่างโค้ดแสดงวิธีสร้างฟีดข้อเสนอ DINING จากนั้นตัวอย่างจะแสดงวิธีแปลงฟีดเป็น JSON
Python API อนุญาตให้เริ่มต้นใช้งานออบเจ็กต์ที่ซ้อนกันแบบเลื่อนเวลาได้โดยการตั้งค่าพร็อพเพอร์ตี้Java
- เพิ่มทรัพยากร Dependency protobuf-java และ protobuf-java-util ลงในโปรเจ็กต์โดยใช้ Maven หรือ Gradle ตามที่อธิบายไว้ที่นี่
- สร้างซอร์สโค้ดของไคลเอ็นต์
protoc --java_out=src/main/java offer.proto money.proto dayofweek.proto timeofday.proto date.proto
คุณอาจใช้ protobuf-maven-plugin เพื่อสร้างซอร์สโค้ดในระหว่างการคอมไพล์เมื่อใช้ Maven
หากใช้ Bazel ให้ลองใช้กฎ java_proto_library แทนการเรียกใช้ protoc
ตัวอย่างการใช้งาน
ดูตัวอย่างโปรเจ็กต์ทั้งหมดได้ที่นี่
package com.example.offers; import com.google.protobuf.util.JsonFormat; import ext.maps.booking.feeds.offers.Offer; import ext.maps.booking.feeds.offers.OfferFeed; import ext.maps.booking.feeds.offers.OfferDetails; import ext.maps.booking.feeds.offers.OfferRestrictions; import ext.maps.booking.feeds.offers.Terms; import ext.maps.booking.feeds.offers.ValidityPeriod; import ext.maps.booking.feeds.offers.ValidityRange; import ext.maps.booking.feeds.offers.OfferCategory; import ext.maps.booking.feeds.offers.OfferMode; import ext.maps.booking.feeds.offers.OfferSource; import ext.maps.booking.feeds.offers.ActionType; import ext.maps.booking.feeds.offers.TimeOfDayWindow; import ext.maps.booking.feeds.offers.TimeOfDayRange; import com.google.type.Money; import com.google.protobuf.Timestamp; import com.google.type.DayOfWeek; import com.google.type.TimeOfDay; public class OfferFeedGenerator { // 200 MB public static final int MAX_BYTES_DATA_FILE = 200 * 1024 * 1024; public static void main(String[] args) throws Exception { // Create OfferFeed OfferFeed.Builder feed = OfferFeed.newBuilder(); // Build the offers Offer offer = Offer.newBuilder() .setOfferId("offer-1") .addEntityIds("dining-1") .setOfferSource(OfferSource.OFFER_SOURCE_AGGREGATOR) .setActionType(ActionType.ACTION_TYPE_DINING) .addOfferModes(OfferMode.OFFER_MODE_WALK_IN) .addOfferModes(OfferMode.OFFER_MODE_FREE_RESERVATION) .setOfferCategory(OfferCategory.OFFER_CATEGORY_BASE_OFFER) .setOfferDetails(OfferDetails.newBuilder() .setOfferDisplayText("₹100 off on your order") .setDiscountValue(Money.newBuilder() .setCurrencyCode("INR") .setUnits(100))) .setOfferRestrictions(OfferRestrictions.newBuilder() .setCombinableWithOtherOffers(true) .addCombinableOfferCategories(OfferCategory.OFFER_CATEGORY_ADD_ON_PAYMENT_OFFER) .addCombinableOfferCategories(OfferCategory.OFFER_CATEGORY_ADD_ON_COUPON_OFFER)) .setTerms(Terms.newBuilder() .setRestrictedToCertainUsers(false) .setTermsAndConditions("Valid on all menu items.")) .addValidityPeriods(ValidityPeriod.newBuilder() .setValidPeriod(ext.maps.booking.feeds.offers.ValidityRange.newBuilder() .setValidFromTime(Timestamp.newBuilder().setSeconds(1687062000)) .setValidThroughTime(Timestamp.newBuilder().setSeconds(1956556800))) // // Monday - Thursday Window .addTimeOfDay(ext.maps.booking.feeds.offers.TimeOfDayWindow.newBuilder() .setTimeWindows(ext.maps.booking.feeds.offers.TimeOfDayRange.newBuilder() .setOpenTime(com.google.type.TimeOfDay.newBuilder().setHours(13)) .setCloseTime(com.google.type.TimeOfDay.newBuilder().setHours(23))) .addDayOfWeek(DayOfWeek.MONDAY) .addDayOfWeek(DayOfWeek.TUESDAY) .addDayOfWeek(DayOfWeek.WEDNESDAY) .addDayOfWeek(DayOfWeek.THURSDAY)) // Friday - Sunday Window .addTimeOfDay(ext.maps.booking.feeds.offers.TimeOfDayWindow.newBuilder() .setTimeWindows(ext.maps.booking.feeds.offers.TimeOfDayRange.newBuilder() .setOpenTime(com.google.type.TimeOfDay.newBuilder().setHours(13)) .setCloseTime(com.google.type.TimeOfDay.newBuilder().setHours(23).setMinutes(59).setSeconds(59))) .addDayOfWeek(DayOfWeek.FRIDAY) .addDayOfWeek(DayOfWeek.SATURDAY) .addDayOfWeek(DayOfWeek.SUNDAY)) ) .setOfferUrl("https://www.example-restaurant.com/offer/base_offer_1") .setImageUrl("https://www.example-restaurant.com/images/offer_base.jpg") .build(); // Example testing for offer feed size // Protocol buffer message must be less than 2 GiB // https://protobuf.dev/programming-guides/proto-limits/ // It is recommended to not exceed 200 MB, as there is an Actions // Center limit of 200 MB per file after compression. int offerSize = offer.getSerializedSize(); int currentFeedSize = feed.build().getSerializedSize(); if (currentFeedSize + offerSize < MAX_BYTES_DATA_FILE) { feed.addData(offer); } else { // 1. Serialize and save the current 'feed' to a file // 2. Reset 'feed' to a new empty OfferFeed // 3. Add the 'offer' to the new feed } // Serialize to JSON String jsonOutput = JsonFormat.printer() .preservingProtoFieldNames() .print(feed); System.out.println(jsonOutput); } }
ตัวอย่างโค้ดแสดงวิธีสร้างฟีดข้อเสนอ DINING จากนั้นตัวอย่างจะแสดงวิธีแปลงฟีดเป็น JSON
Go
- ติดตั้งปลั๊กอิน protoc สำหรับ Go
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
อัปเดตตัวแปรสภาพแวดล้อม PATH ให้รวมปลั๊กอิน protoc-gen-go เช่น
export PATH="$PATH:$(go env GOPATH)/bin"
- เริ่มต้นแอปและสร้างซอร์สโค้ดไคลเอ็นต์
go mod init feed/app mkdir generated protoc --go_out=./generated/ offer.proto money.proto dayofweek.proto timeofday.proto date.proto
หากใช้ Bazel ให้ลองใช้กฎ go_proto_library แทนการเรียกใช้ protoc
ตัวอย่างการใช้งาน
ดูตัวอย่างโปรเจ็กต์ทั้งหมดได้ที่นี่
package main import ( pb "feed/app/generated/ext/maps/booking/offers/proto" "fmt" "log" money "google.golang.org/genproto/googleapis/type/money" timestamppb "google.golang.org/protobuf/types/known/timestamppb" dayofweek "google.golang.org/genproto/googleapis/type/dayofweek" timeofday "google.golang.org/genproto/googleapis/type/timeofday" proto "google.golang.org/protobuf/proto" "google.golang.org/protobuf/encoding/protojson" ) func main() { // 200 MB feed file size limit const MaxBytesDataFile = 200 * 1024 * 1024 // Create OfferFeed with offers feed := &pb.OfferFeed{} // Build the offers offer := &pb.Offer{ OfferId: "offer-1", EntityIds: []string{"dining-1"}, OfferSource: pb.OfferSource_OFFER_SOURCE_AGGREGATOR, ActionType: pb.ActionType_ACTION_TYPE_DINING, OfferModes: []pb.OfferMode{ pb.OfferMode_OFFER_MODE_WALK_IN, pb.OfferMode_OFFER_MODE_FREE_RESERVATION, }, OfferCategory: pb.OfferCategory_OFFER_CATEGORY_BASE_OFFER, OfferDetails: &pb.OfferDetails{ OfferDisplayText: "₹100 off on your order", OfferSpecification: &pb.OfferDetails_DiscountValue{ DiscountValue: &money.Money{ CurrencyCode: "INR", Units: 100, }, }, }, OfferRestrictions: &pb.OfferRestrictions{ CombinableWithOtherOffers: true, CombinableOfferCategories: []pb.OfferCategory{ pb.OfferCategory_OFFER_CATEGORY_ADD_ON_PAYMENT_OFFER, pb.OfferCategory_OFFER_CATEGORY_ADD_ON_COUPON_OFFER, }, }, Terms: &pb.Terms{ RestrictedToCertainUsers: false, TermsAndConditions: "Valid on all menu items.", }, ValidityPeriods: []*pb.ValidityPeriod{ { ValidPeriod: &pb.ValidityRange{ ValidFromTime: ×tamppb.Timestamp{Seconds: 1687062000}, ValidThroughTime: ×tamppb.Timestamp{Seconds: 1956556800}, }, TimeOfDay: []*pb.TimeOfDayWindow{ // Monday - Thursday Window { TimeWindows: &pb.TimeOfDayRange{ OpenTime: &timeofday.TimeOfDay{Hours: 13}, CloseTime: &timeofday.TimeOfDay{Hours: 23}, }, DayOfWeek: []dayofweek.DayOfWeek{ dayofweek.DayOfWeek_MONDAY, dayofweek.DayOfWeek_TUESDAY, dayofweek.DayOfWeek_WEDNESDAY, dayofweek.DayOfWeek_THURSDAY, }, }, // Friday - Sunday Window { TimeWindows: &pb.TimeOfDayRange{ OpenTime: &timeofday.TimeOfDay{Hours: 13}, CloseTime: &timeofday.TimeOfDay{ Hours: 23, Minutes: 59, Seconds: 59, }, }, DayOfWeek: []dayofweek.DayOfWeek{ dayofweek.DayOfWeek_FRIDAY, dayofweek.DayOfWeek_SATURDAY, dayofweek.DayOfWeek_SUNDAY, }, }, }, }, }, OfferUrl: "https://www.example-restaurant.com/offer/base_offer_1", ImageUrl: "https://www.example-restaurant.com/images/offer_base.jpg", } // Example testing for feed size // Protocol buffer message must be less than 2 GiB // https://protobuf.dev/programming-guides/proto-limits/ // It is recommended to not exceed 200 MB, as there is an Actions // Center limit of 200 MB per file after compression. offerSize := proto.Size(offer) currentFeedSize := proto.Size(feed) if currentFeedSize + offerSize < MaxBytesDataFile { feed.Data = append(feed.Data, offer) } else { // 1. Serialize and save the current 'feed' to a file // 2. Reset 'feed' to a new empty OfferFeed // 3. Add the 'offer' to the new feed } // Serialize to JSON marshaler := protojson.MarshalOptions{ UseProtoNames: true, } jsonOutput, err := marshaler.Marshal(feed) if err != nil { log.Fatalf("Failed to marshal feed: %v", err) } fmt.Println(string(jsonOutput)) }
ตัวอย่างโค้ดแสดงวิธีสร้างฟีดข้อเสนอ DINING จากนั้นตัวอย่างจะแสดงวิธีแปลงฟีดเป็น JSON
TypeScript
-
ติดตั้งปลั๊กอิน protoc ของ TypeScript
โปรดทราบว่า ts-proto ไม่ใช่โปรเจ็กต์ของ Google ที่ได้รับการสนับสนุนอย่างเป็นทางการnpm init npm i -D typescript npm i ts-proto
- สร้างไดเรกทอรีเอาต์พุตและสร้างซอร์สโค้ดไคลเอ็นต์
สร้างไดเรกทอรีเอาต์พุตของ Proto: src/generated
protoc --plugin="./node_modules/.bin/protoc-gen-ts_proto" --ts_proto_opt=useOptionals=all --ts_proto_opt=snakeToCamel=false --ts_proto_opt=onlyTypes=true --ts_proto_out="./src/generated" offer.proto money.proto dayofweek.proto timeofday.proto date.proto
หากใช้ Bazel ให้ลองใช้กฎ js_proto_library แทนการเรียกใช้ protoc
ตัวอย่างการใช้งาน
ดูตัวอย่างโปรเจ็กต์ทั้งหมดได้ที่นี่
import { Offer, OfferFeed, OfferSource, ActionType, OfferMode, OfferCategory } from "./generated/offer"; // Path to your generated types import { Money } from "./generated/money"; import { DayOfWeek } from "./generated/dayofweek"; import { TimeOfDay } from "./generated/timeofday"; import { Timestamp } from "./generated/google/protobuf/timestamp"; import { Duration } from "./generated/google/protobuf/duration"; // 200 MB Limit const MAX_BYTES_DATA_FILE = 200 * 1024 * 1024; function generateOfferFeed() { // Build the Offer object const offer: Offer = { offer_id: "offer-1", entity_ids: ["dining-1"], offer_source: OfferSource.OFFER_SOURCE_AGGREGATOR, action_type: ActionType.ACTION_TYPE_DINING, offer_modes: [ OfferMode.OFFER_MODE_WALK_IN, OfferMode.OFFER_MODE_FREE_RESERVATION, ], offer_category: OfferCategory.OFFER_CATEGORY_BASE_OFFER, offer_details: { offer_display_text: "₹100 off on your order", // Set 'oneof' field: discountValue discount_value: { currency_code: "INR", units: 100, }, }, offer_restrictions: { combinable_with_other_offers: true, combinable_offer_categories: [ OfferCategory.OFFER_CATEGORY_ADD_ON_PAYMENT_OFFER, OfferCategory.OFFER_CATEGORY_ADD_ON_COUPON_OFFER, ], }, terms: { restricted_to_certain_users: false, terms_and_conditions: "Valid on all menu items.", }, validity_periods: [ { valid_period: { valid_from_time: new Date(1687062000000), valid_through_time: new Date(1956556800000), }, time_of_day: [ // Monday - Thursday Window { time_windows: { open_time: { hours: 13, minutes: 0, seconds: 0, nanos: 0 }, close_time: { hours: 23, minutes: 0, seconds: 0, nanos: 0 }, }, day_of_week: [ DayOfWeek.MONDAY, DayOfWeek.TUESDAY, DayOfWeek.WEDNESDAY, DayOfWeek.THURSDAY, ], }, // Friday - Sunday Window { time_windows: { open_time: { hours: 13, minutes: 0, seconds: 0, nanos: 0 }, close_time: { hours: 23, minutes: 59, seconds: 59, nanos: 0 }, }, day_of_week: [ DayOfWeek.FRIDAY, DayOfWeek.SATURDAY, DayOfWeek.SUNDAY, ], }, ], }, ], offer_url: "https://www.example-restaurant.com/offer/base_offer_1", image_url: "https://www.example-restaurant.com/images/offer_base.jpg", }; // 2. Initialize the Feed const feed: OfferFeed = { data: [] }; // 3. Size checking // encode().finish() returns a Uint8Array (the binary representation) const offerSize = new Blob([JSON.stringify(offer)]).size; const currentFeedSize = new Blob([JSON.stringify(feed)]).size; if (currentFeedSize + offerSize < MAX_BYTES_DATA_FILE) { if (feed.data == undefined) { feed.data = [offer]; } else { feed.data.push(offer); } } else { // Logic for file rotation goes here console.log("Feed size limit reached. Rotate file."); } // 4. Serialize to JSON // In JS/TS, we often just use JSON.stringify for the plain object // if you used proto3 and want to ensure proper enum/timestamp formatting, // use the library's toJSON method. const jsonOutput = JSON.stringify(feed); console.log(jsonOutput); } generateOfferFeed();
ตัวอย่างโค้ดแสดงวิธีสร้างฟีดข้อเสนอ DINING จากนั้นตัวอย่างจะแสดงวิธีแปลงฟีดเป็น JSON
หมายเหตุ: มีส่วนขยายคอมไพเลอร์ Protoc ของบุคคลที่สามเพื่อสร้าง JSON Schema จาก Proto Schema หากต้องการทำงานกับไฟล์ JSON Schema คุณสามารถดูตัวอย่างได้ที่นี่