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

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

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

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

יצירת קוד מקור

  1. התקנת ספריית ה-protobuf של Python
    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

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

    

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

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

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

ה-API של Python מאפשר אתחול עצל של אובייקטים בתצוגת עץ על ידי הגדרת המאפיינים.
  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}]
};

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

  

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

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

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

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

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

    אם אתם משתמשים ב-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 {

  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));

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

    System.out.println(feedJSON);
  }
}

    

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

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

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

  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"
)

func main() {

	//create a menu component
	menu := &pb.Menu{
		MenuId:      "menu1",
		MerchantIds: []string{"dining-1"},
		DisplayName: &pb.TextField{
			Text: []*localized_text.LocalizedText{{
				Text:         "Menu",
				LanguageCode: "en-us",
			}},
		},
		Language: "en-us",
		LastMerchantUpdateTime: &timestamppb.Timestamp{
			Seconds: 1633621547,
		},
	}

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

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

	//create a menu item option
	option := &pb.MenuItemOption{
		MenuItemOptionId: "breadstick-sm",
		Value: &pb.MenuItemOptionProperty{
			PropertyType: pb.MenuItemOptionProperty_SIZE,
			Value: &pb.MenuItemOptionProperty_TextVal{
				TextVal: &pb.TextField{
					Text: []*localized_text.LocalizedText{{
						Text:         "Small",
						LanguageCode: "en-us",
					}},
				},
			},
		},
		OfferSet: &pb.OfferSet{
			Offers: []*pb.Offer{{
				Price: &money.Money{
					CurrencyCode: "USD",
					Units:        8,
					Nanos:        0,
				},
			}},
		},
	}

	//create feed
	feed := &pb.FoodMenuFeed{
		Data: []*pb.MenuComponent{{
			Type: &pb.MenuComponent_Menu{
				Menu: menu,
			},
		},
			{
				Type: &pb.MenuComponent_Section{
					Section: section,
				},
			},
			{
				Type: &pb.MenuComponent_Item{
					Item: item,
				},
			},
			{
				Type: &pb.MenuComponent_Option{
					Option: option,
				},
			}},
	}

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


    

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

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

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