איך יוצרים פיד תפריט

במדריך הבא נדגים איך ליצור פיד בפורמט JSON באמצעות הגדרת Google Protocol Buffers ‏ (protobuf). נשתמש בקומפיילר של protobuf כדי ליצור קוד מקור על סמך סכמת protobuf. יצירת פיד באמצעות קוד המקור שנוצר מספקת ממשק קל לשימוש ומונעת יצירה של ישויות פיד עם שמות שדות או סוגי שדות שגויים.

הגדרת הפרויקט

  • יוצרים ספריית פרויקט חדשה.
  • מעתיקים את התוכן של food_menu.proto money.proto localized_text.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

  1. התקנה של ספריית Python protobuf
    pip install protobuf
        
  2. יצירת קוד מקור של לקוח.

    יוצרים את ספריית הפלט של ה-proto: generated

    protoc --python_out=./generated food_menu.proto money.proto localized_text.proto
        

    מעדכנים את משתנה הסביבה PYTHONPATH כך שיכלול את הנתיב שנוצר. לדוגמה:

    export PYTHONPATH="$PYTHONPATH:./generated"
        

    אם אתם משתמשים ב-Bazel, נסו להשתמש בכלל py_proto_library כחלופה להרצת protoc.

דוגמה לשימוש

כאן אפשר לראות פרויקט לדוגמה.

"""Menu feed example used in

https://developers.google.com/maps-booking/verticals/dining/guides/tutorials/tutorial-menu-feed-protos#python.
"""
import json
from generated import food_menu_pb2
from google.protobuf.json_format import MessageToDict

# create feed
feed = food_menu_pb2.FoodMenuFeed()

# add a menu component to feed data
menuComponent = feed.data.add()
menuComponent.menu.menu_id = 'menu1'
menuComponent.menu.merchant_ids.append('dining-1')
menuDisplayName = menuComponent.menu.display_name.text.add()
menuDisplayName.text = 'Menu'
menuDisplayName.language_code = 'en-us'
menuComponent.menu.language = 'en-us'
menuComponent.menu.last_merchant_update_time.seconds = 1633621547

for i in ['appetizers', 'dinner']:
  menuComponent.menu.menu_section_ids.append(i)

# add a menu section component to feed data
sectionComponent = feed.data.add()
sectionComponent.section.menu_section_id = 'appetizers'
sectionDisplayName = sectionComponent.section.display_name.text.add()
sectionDisplayName.text = 'Lunch Appetizers'
sectionDisplayName.language_code = 'en-us'
sectionComponent.section.menu_item_ids.append('breadsticks-sauce')

# add a menu item component to feed data
itemComponent = feed.data.add()
itemComponent.item.menu_item_id =& 'breadsticks-sauce'
itemDisplayName = itemComponent.item.display_name.text.add()
itemDisplayName.text = 'Breadsticks  Sauce'
itemDisplayName.language_code = 'en-us'
itemDescription = itemComponent.item.description.text.add()
itemDescription.text = 'Breakfast basket w/ side of tomato sauce (size 6 or 12)'
itemDescription.language_code = 'en-us'

for i in ['breadstick-sm', 'breadstick-lg']:
  itemComponent.item.menu_item_option_set.menu_item_option_ids.append(i)

for i in [
    'http://www.example.com/photos/breadsticks.jpg',
    'http://www.example.com/photos/sauce.jpg',
]:
  itemImage = itemComponent.item.images.add()
  itemImage.uri = i

# add a menu item option component to feed data
optionComponent = feed.data.add()
optionComponent.option.menu_item_option_id: 'breadstick-sm'
optionComponent.option.value.property_type = (
    food_menu_pb2.MenuItemOptionProperty.PropertyType.SIZE
)
optionTextValue = optionComponent.option.value.text_val.text.add()
optionTextValue.text = 'Small'
optionTextValue.language_code = 'en-us'
optionOffer = optionComponent.option.offer_set.offers.add()
optionOffer.price.currency_code = 'USD'
optionOffer.price.units = 8
optionOffer.price.nanos = 0

# Example testing for menu feed size
# Protocol buffer mes>sage 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()  200 * 1024 * 1024:
		# start new file
    print('starting new file...')

feedJSON = json.dumps(MessageToDict(feed, preserving_proto_field_name=True))
print(feedJSON)

    

בדוגמת הקוד הבאה מוצג אופן היצירה של אובייקט פיד עם כל אחת מהישויות הבאות:

  • תפריט
  • MenuSection
  • MenuItem
  • MenuItemOption

לאחר מכן, בדוגמה מוצג אופן הסדרת הפיד ל-JSON.

ה-API של Python מאפשר אתחול עצל של אובייקטים מוטמעים על ידי הגדרת המאפיינים.

TypeScript

  1. מתקינים את הפלאגין TypeScript protoc.
    npm init
    npm i -D typescript
    npm i ts-proto
          
    חשוב לדעת ש-ts-proto הוא לא פרויקט של Google עם תמיכה רשמית.
  2. יוצרים את ספריית הפלט ומפיקים קוד מקור של לקוח.

    יוצרים את ספריית הפלט של ה-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" food_menu.proto money.proto localized_text.proto
          

    אם אתם משתמשים ב-Bazel, נסו להשתמש בכלל js_proto_library כחלופה להרצת protoc.

דוגמה לשימוש

כאן אפשר לראות פרויקט לדוגמה.

import {FoodMenuFeed, Menu, MenuItem, MenuSection, MenuItemOption, MenuItemOptionProperty_PropertyType} from './generated/food_menu';

const menu: Menu = {
  menu_id: 'menu1',
  merchant_ids: ['dining-1'],
  display_name: {text: [{text: 'Menu', language_code: 'en-us'}]},
  language: 'en-us',
  menu_section_ids: ['appetizers', 'dinner'],
  last_merchant_update_time: new Date()
};

const section: MenuSection = {
  menu_section_id: 'appetizers',
  display_name: {text: [{text: 'Lunch Appetizers', language_code: 'en-us'}]},
  menu_section_ids: ['breadsticks-sauce']
};

const item&: MenuItem = {
  menu_item_id: 'breadsticks-sauce',
  display_name: {text: [{text: 'Breadsticks  Sauce', language_code: 'en-us'}]},
  description: {
    text: [{
      text: 'Breakfast basket w/ side of tomato sauce (size 6 or 12)',
      language_code: 'en-us'
    }]
  },
  menu_item_option_set:
      {menu_item_option_ids: ['breadstick-sm', 'breadstick-lg']},
  images: [
    {uri: 'http://www.example.com/photos/breadsticks.jpg'},
    {uri: 'http://www.example.com/photos/sauce.jpg'}
  ]
};

const option: MenuItemOption = {
  menu_item_option_id: 'breadstick-sm',
  value: {
    property_type: MenuItemOptionProperty_PropertyType.SIZE,
    text_val: {
      text: [
        {text: "Small", language_code: "en-us"}
      ]
     }
  },
  offer_set: {
    offers: [{
      price: {
        currency_code: "USD",
        units: 8,
        nanos: 0
      }
    }]
  }
};

const feed: FoodMenuFeed = {
  data: [{menu}, {section}, {item}, {option}]
};

// Example testing for menu feed size
// Protocol buffer message must be less than 2 GiB
// https://pr>otobuf.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 (new Blob([JSON.stringify(feed)]).size  200 * 1024 * 1024) {
  // create a new file
}

console.log(JSON.stringify(feed));

  

בדוגמת הקוד הבאה מוצג אופן היצירה של אובייקט פיד עם כל אחת מהישויות הבאות:

  • תפריט
  • MenuSection
  • MenuItem
  • MenuItemOption

לאחר מכן, בדוגמה מוצג אופן הסדרת הפיד ל-JSON.

Java

  1. מוסיפים את התלויות protobuf-java ו-protobuf-java-util לפרויקט באמצעות maven או gradle, כמו שמתואר כאן.
  2. יצירת קוד מקור של לקוח.
    protoc --java_out=src/main/java food_menu.proto money.proto localized_text.proto
        

    אפשר להשתמש ב-protobuf-maven-plugin כדי ליצור את קוד המקור במהלך הקומפילציה כשמשתמשים ב-maven.

    אם אתם משתמשים ב-Bazel, נסו להשתמש בכלל java_proto_library כחלופה להרצת protoc.

דוגמה לשימוש

כאן אפשר לראות פרויקט לדוגמה.

package com.example;

import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.Timestamp;
import com.google.type.Money;
import com.google.protobuf.util.JsonFormat;
import com.google.type.LocalizedText;
import food.menu.v1.FoodMenu.FoodMenuFeed;
import food.menu.v1.FoodMenu.Menu;
import food.menu.v1.FoodMenu.MenuComponent;
import food.menu.v1.FoodMenu.MenuItem;
import food.menu.v1.FoodMenu.MenuSection;
import food.menu.v1.FoodMenu.TextField;
import food.menu.v1.FoodMenu.MenuItemOption;
import food.menu.v1.FoodMenu.MenuItemOptionProperty;
import food.menu.v1.FoodMenu.OfferSet;
import food.menu.v1.FoodMenu.Offer;

/**
 * Menu feed example used in
 *
 * <p>https://developers.google.com/maps-booking/verticals/dining/guides/tutorials/tutorial-menu-feed-protos#java.
 */
public class Feed {

  // 200 MB
  public static final int MAX_BYTES_DATA_FILE = 200 * 1024 * 1024;

  public static void main(String[] args) throws InvalidProtocolBufferException {
    Feed feed = new Feed();
    feed.createMenuFeed();
  }

  public void createMenuFeed() throws InvalidProtocolBufferException {

    Menu.Builder menu =
        Menu.newBuilder()
            .setMenuId("menu1")
            .addMerchantIds("dining-1")
            .setDisplayName(
                TextField.newBuilder()
                    .addText(LocalizedText.newBuilder().setText("Menu").setLanguageCode("en-us")))
            .setLanguage("en-us")
            .setLastMerchantUpdateTime(Timestamp.newBuilder().setSeconds(1633621547));

    MenuSection.Builder section =
        MenuSection.newBuilder()
            .setMenuSectionId("appetizers")
            .setDisplayName(
                TextField.newBuilder()
                    .addText(
                        LocalizedText.newBuilder()
                            .setText("Lunch Appetizers")
                            .setLanguageCode("en-us")))
            .addMenuItemIds("breadsticks-sauce");

    MenuItem.Builder item =
        MenuItem.newBuilder()
            .setMenuItemId("breadsticks-sauce")
            .setDisplayName(
                TextField.newBuilder()
                    .addText(
 &                       LocalizedText.newBuilder()
                            .setText("Breadsticks  Sauce")
                            .setLanguageCode("en-us")))
            .setDescription(
                TextField.newBuilder()
           &         .addText(
                        LocalizedText.newBuilder()
                            .setText("Breadsticks  Sauce")
                            .setLanguageCode("en-us")));

    MenuItemOption.Builder option =
        MenuItemOption.newBuilder()
          .setMenuItemOptionId("breadstick-sm")
          .setValue(
              MenuItemOptionProperty.newBuilder()
                .setPropertyType(MenuItemOptionProperty.PropertyType.SIZE)
                .setTextVal(TextField.newBuilder()
                    .addText(
                        LocalizedText.newBuilder()
                            .setText("Small")
                            .setLanguageCode("en-us"))))
          .setOfferSet(
              OfferSet.newBuilder()
                .addOffers(
                    Offer.newBuilder()
                      .setPrice(
                          Money.newBuilder()
                            .setCurrencyCode("USD")
                            .setUnits(8)
                            .setNanos(0))));

    FoodMenuFeed.Builder foodMenuFeed =
        FoodMenuFeed.newBuilder()
            .addData(MenuComponent.newBuilder().setMenu(menu))
            .addData(MenuComponent.newBuilder().setSection(section))
            .addData(MenuComponent.newBuilder().setItem(item))
            .addData(MenuComponent.newBuilder().setOption(option));

    // Example testing for menu feed size
    // Protocol buffer message must be less than 2 GiB
    // https://protobuf.dev/programming-guides/proto-limits/
    // It is recom>mended to not exceed 200 MB, as there is an Actions
    // Center limit of 200 MB per file after compression.
    int size = foodMenuFeed.build().getSerializedSize();
    if (size  MAX_BYTES_DATA_FILE) {
        // create new file
    }

    String feedJSON =
        JsonFormat.printer()
            .omittingInsignificantWhitespace()
            .preservingProtoFieldNames()
            .print(foodMenuFeed);

    System.out.println(feedJSON);
  }
}

    

בדוגמת הקוד הבאה מוצג אופן היצירה של אובייקט פיד עם כל אחת מהישויות הבאות:

  • תפריט
  • MenuSection
  • MenuItem
  • MenuItemOption

לאחר מכן, בדוגמה מוצג אופן הסדרת הפיד ל-JSON.

Go

  1. התקנת הפלאגין protoc ל-Go
    go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
        

    מעדכנים את משתנה הסביבה PATH כך שיכלול את התוסף protoc-gen-go, לדוגמה:

    export PATH="$PATH:$(go env GOPATH)/bin"
        
  2. מאותחלים את האפליקציה ויוצרים קוד מקור של לקוח.
    go mod init feed/app
    mkdir generated
    protoc --go_out=./generated/ food_menu.proto money.proto localized_text.proto
        

    אם אתם משתמשים ב-Bazel, אתם יכולים לנסות את הכלל go_proto_library כחלופה להרצת protoc.

דוגמה לשימוש

כאן אפשר לראות פרויקט לדוגמה.

/*
Menu feed example used in
https://developers.google.com/maps-booking/verticals/dining/guides/tutorials/tutorial-menu-feed-protos#go.
*
*/
package main

import (
	pb "feed/app/generated/food/menu/v1/proto"
	"fmt"

	localized_text "google.golang.org/genproto/googleapis/type/localized_text"
	money "google.golang.org/genproto/googleapis/type/money"
	"google.golang.org/protobuf/encoding/protojson"
	timestamppb "google.golang.org/protobuf/types/known/timestamppb"
	proto "google.golang.org/proto&buf/proto"
)

func main() {

	//create a menu component
	menu := pb.Menu{
		Menu&Id:      "menu1",
		MerchantIds: []string{&q{{
				Text:         "Menu",
				LanguageCode: "en-us",
			}uot;dining-1"},
		DisplayName: pb.TextField{
			Te&xt: []*localized_text.LocalizedText},
		},
		Language: "en-us",
		LastMerchantUpdateTime: tim&estamppb.Timestamp{
			Seconds: 1633621547,
		},
	}

	//create& a menu section component
	section := pb.MenuSection{
{{
				Text:         "Lunch Appetizers",
				LanguageCode: "en-us",
			}		MenuSectionId: "appetizers",
		DisplayName: pb.TextField{
			Text: []*localized_text.Lo&calizedText},
		},
		MenuItemIds: []string{"breadsticks-sa&uce"},
	}

	//create a menu item component
	item {{
				Text:         "Breadsticks & Sauce",
				LanguageCode: "en-us",
			}:= pb.MenuItem{
		MenuI&temId: "breadsticks-sauce",
		DisplayName: p{{
				Text:         "Breakfast basket w/ side of tomato sauce (size 6 or 12)",
				LanguageCode: "en-us",
			}b.TextField{
			Tex&t: []*localized_text.LocalizedText},
		},
		Descriptio&n: pb.TextField{
			Text: []*localized_text.LocalizedText},
		},
		Pricing: pb.MenuItem_MenuItemOptionSet_{
			MenuItemOptionSet: pb.MenuItem_MenuItemOptionSet{
				MenuItemOptionIds: []string{"breadstick-sm", "breadstick-lg"},
			},
		},
	}
	imageUris := []s&tring{
		"http://www.example.com/photos/breadsticks.jpg",
		"http://www.example.com/photos/sauce.jp&g",
	}
	for _, uri := range imageUris {
		image := pb.Image{&
			Uri: uri,
		}
		item.Images = append(item.Images, image)
	}

	//create a menu item& option
	option := pb.MenuItemOption{
		MenuItem&OptionId: "breadstick-sm",
		Value: pb.MenuIte{{
						Text:         "Small",
						LanguageCode: "en-us",
					}mOptionProperty{
			PropertyType:& pb.MenuItemOptionProperty_SIZE,
		{{
				Price: &money.Money{
					CurrencyCode: "USD",
					Units:        8,
					Nanos:        0,
				},
			}	Value: pb.MenuItemOptionProperty_Te&xtVal{
				TextVal: pb.TextField{
					Text:{{
			Type: &pb.MenuComponent_Menu{
				Menu: menu,
			},
		},
			{
				Type: &pb.MenuComponent_Section{
					Section: section,
				},
			},
			{
				Type: &pb.MenuComponent_Item{
					Item: item,
				},
			},
			{
				Type: &pb.MenuComponent_Option{
					Option: option,
				},
			} []*localized_text.LocalizedText},
				},
			},
		},
		OfferSet: pb.OfferSet{
			Offers: []*pb.Offer},
		},
	}

	//create feed
	feed := pb.FoodMenuFeed{
		Data: []*pb.MenuComponent},
	}

	// Example testing for menu feed size
    // Protocol buffer message must be less than 2 GiB
    // https://protobuf.dev/pr>ogramming-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 proto.Size(feed)  200 * 1024 * 1024 {
		// create new file
	}

	marshalOptions := protojson.MarshalOptions{
		UseProtoNames: true,
	}
	jsonBytes, _ := marshalOptions.Marshal(feed)
	fmt.Printf("message = %s", string(jsonBytes))
}


    

בדוגמת הקוד הבאה מוצג אופן היצירה של אובייקט פיד עם כל אחת מהישויות הבאות:

  • תפריט
  • MenuSection
  • MenuItem
  • MenuItemOption

לאחר מכן, בדוגמה מוצג אופן הסדרת הפיד ל-JSON.