Jak utworzyć plik danych z ofertami

W tym samouczku pokażemy, jak utworzyć plik danych w formacie JSON za pomocą definicji buforów protokołu Google (protobuf). Użyjemy kompilatora protobuf, aby wygenerować kod źródłowy na podstawie schematu protobuf. Tworzenie pliku danych za pomocą wygenerowanego kodu źródłowego zapewnia łatwy interfejs i zapobiega tworzeniu elementów pliku danych z nieprawidłowymi nazwami lub typami pól.

Konfigurowanie projektu

  • Utwórz nowy katalog projektu.
  • Skopiuj zawartość plików offer.proto money.proto dayofweek.proto timeofday.proto date.proto z definicji proto jako nowe pliki do katalogu głównego projektu.
  • Instalowanie kompilatora protoc

    Aby wygenerować kod źródłowy z plików .proto, potrzebujesz kompilatora protoc. Pobierz najnowszy gotowy plik binarny ze strony wersji w GitHubie Protocol Buffers.

    Wyodrębnij plik ZIP i dodaj ścieżkę do folderu bin do zmiennej środowiskowej PATH, na przykład:

    unzip protoc-22.0-linux-x86_64.zip -d /usr/local/protoc
    export PATH="$PATH:/usr/local/protoc/bin"
      

Generowanie kodu źródłowego

Python

  1. Instalowanie biblioteki protobuf w Pythonie
    pip install protobuf
        
  2. Wygeneruj kod źródłowy klienta.

    Utwórz katalog wyjściowy proto: generated

    protoc --python_out=./generated offer.proto money.proto dayofweek.proto timeofday.proto date.proto
        

    Zaktualizuj zmienną środowiskową PYTHONPATH, aby uwzględnić wygenerowaną ścieżkę, np.:

    export PYTHONPATH="$PYTHONPATH:./generated"
        

    Jeśli używasz Bazela, wypróbuj regułę py_proto_library jako alternatywę dla uruchamiania protoc.

Przykład użycia

Pełny przykładowy projekt znajdziesz tutaj.

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

Przykład kodu pokazuje, jak utworzyć plik danych z ofertami dotyczącymi restauracji. Następnie pokazano, jak serializować plik danych do formatu JSON.

Interfejs API Pythona umożliwia leniwą inicjację zagnieżdżonych obiektów przez ustawienie właściwości.

Java

  1. Dodaj do projektu zależności protobuf-javaprotobuf-java-util za pomocą narzędzia Maven lub Gradle zgodnie z instrukcjami podanymi tutaj.
  2. Wygeneruj kod źródłowy klienta.
    protoc --java_out=src/main/java offer.proto money.proto dayofweek.proto timeofday.proto date.proto
        

    Jeśli używasz narzędzia Maven, możesz użyć wtyczki protobuf-maven-plugin, aby wygenerować kod źródłowy w czasie kompilacji.

    Jeśli używasz Bazela, zamiast uruchamiać protoc, wypróbuj regułę java_proto_library.

Przykład użycia

Pełny przykładowy projekt znajdziesz tutaj.

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

    

Przykład kodu pokazuje, jak utworzyć plik danych z ofertami dotyczącymi restauracji. Następnie pokazano, jak serializować plik danych do formatu JSON.

Go

  1. Instalowanie wtyczki protoc dla Go
    go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
        

    Zaktualizuj zmienną środowiskową PATH, aby uwzględnić wtyczkę protoc-gen-go, np.:

    export PATH="$PATH:$(go env GOPATH)/bin"
        
  2. Zainicjuj aplikację i wygeneruj kod źródłowy klienta.
    go mod init feed/app
    mkdir generated
    protoc --go_out=./generated/ offer.proto money.proto dayofweek.proto timeofday.proto date.proto
        

    Jeśli używasz Bazel, wypróbuj regułę go_proto_library jako alternatywę dla uruchamiania protoc.

Przykład użycia

Pełny przykładowy projekt znajdziesz tutaj.

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:    &timestamppb.Timestamp{Seconds: 1687062000},
					ValidThroughTime: &timestamppb.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))
}
    

Przykład kodu pokazuje, jak utworzyć plik danych z ofertami dotyczącymi restauracji. Następnie pokazano, jak serializować plik danych do formatu JSON.

TypeScript

  1. Zainstaluj wtyczkę protoc TypeScript.
    npm init
    npm i -D typescript
    npm i ts-proto
          
    Pamiętaj, że ts-proto nie jest oficjalnie obsługiwanym projektem Google.
  2. Utwórz katalog wyjściowy i wygeneruj kod źródłowy klienta.

    Utwórz katalog wyjściowy 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
          

    Jeśli używasz Bazela, wypróbuj regułę js_proto_library jako alternatywę dla uruchamiania protoc.

Przykład użycia

Pełny przykładowy projekt znajdziesz tutaj.

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

  

Przykład kodu pokazuje, jak utworzyć plik danych z ofertami dotyczącymi restauracji. Następnie pokazano, jak serializować plik danych do formatu JSON.

Uwaga: istnieją rozszerzenia kompilatora protoc innych firm, które umożliwiają generowanie schematu JSON ze schematu proto. Jeśli wolisz pracować z plikami schematu JSON, możesz zapoznać się z przykładowym plikiem tutaj.