HINWEIS: Diese Website wurde eingestellt. Die Website wird nach dem 31. Januar 2023 eingestellt. Der Traffic wird auf die neue Website unter https://Protop.dev weitergeleitet. In der Zwischenzeit werden nur Änderungen an aufgeführt.

Sprachleitfaden (proto3)

Mit Sammlungen den Überblick behalten Sie können Inhalte basierend auf Ihren Einstellungen speichern und kategorisieren.

In diesem Leitfaden wird beschrieben, wie Sie die Protokollpuffersprache, um Ihre Protokollpufferdaten zu strukturieren, einschließlich der .proto-Dateisyntax, und wie Sie Datenzugriffsklassen aus Ihren .proto-Dateien generieren. Sie deckt die Proto3-Version der Protokollpuffersprache ab. Informationen zur proto2-Syntax finden Sie im Proto2-Sprachleitfaden.

Dies ist ein Referenzleitfaden mit einem detaillierten Beispiel, in dem viele der in diesem Dokument beschriebenen Funktionen verwendet werden. Weitere Informationen finden Sie in der Anleitung für die gewählte Sprache (derzeit nur proto2, weitere proto3-Dokumentation folgt).

Nachrichtentyp definieren

Sehen wir uns zuerst ein sehr einfaches Beispiel an. Angenommen, Sie möchten ein Nachrichtenformat für Suchanfragen definieren, bei dem jede Suchanfrage einen Abfragestring, eine bestimmte Ergebnisseite und eine Reihe von Ergebnissen pro Seite enthält. Hier ist die Datei .proto, mit der Sie den Nachrichtentyp definieren.

syntax = "proto3";

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
}
  • Die erste Zeile der Datei gibt an, dass Sie die Syntax proto3 verwenden: Wenn Sie dies nicht tun, wird davon ausgegangen, dass Sie den Protokollpuffer-Compiler proto2 verwenden. Dies muss die erste nicht leere Kommentarzeile der Datei sein.
  • Die Nachrichtendefinition SearchRequest gibt drei Felder (Name/Wert-Paare) an – eines für jedes Datenelement, das Sie in diesen Nachrichtentyp aufnehmen möchten. Jedes Feld enthält einen Namen und einen Typ.

Feldtypen angeben

Im Beispiel oben sind alle Felder skalare Typen: zwei Ganzzahlen (page_number und result_per_page) und ein String (query). Sie können jedoch auch zusammengesetzte Typen für Ihre Felder angeben, einschließlich Aufzählungen und anderer Nachrichtentypen.

Feldnummern zuweisen

Wie Sie sehen, hat jedes Feld in der Nachrichtendefinition eine eindeutige Zahl. Diese Feldnummern werden verwendet, um die Felder im Binärformat der Nachricht zu identifizieren. Sie sollten nicht geändert werden, wenn Ihr Nachrichtentyp bereits verwendet wird. Beachten Sie, dass Feldnummern im Bereich von 1 bis 15 ein Byte zur Codierung benötigen, einschließlich der Feldnummer und des Feldtyps. Weitere Informationen finden Sie unter Protokollzwischenspeicher-Codierung. Feldnummern im Bereich von 16 bis 2047 benötigen zwei Byte. Daher sollten Sie die Zahlen 1 bis 15 für sehr häufige Nachrichtenelemente reservieren. Denken Sie daran, Platz für häufig auftretende Elemente zu lassen, die möglicherweise in Zukunft hinzugefügt werden.

Die kleinste Feldnummer, die Sie angeben können, ist 1 und die größte ist 229 – 1 oder 536.870.911. Außerdem können Sie nicht die Zahlen 19.000 bis 19.999 (FieldDescriptor::kFirstReservedNumber bis FieldDescriptor::kLastReservedNumber) verwenden, da diese für die Implementierung von Protokollpuffern reserviert sind. Der Protokollpuffer-Compiler wird sich beschweren, wenn Sie eine dieser reservierten Nummern in Ihrer .proto verwenden. Ebenso können Sie keine zuvor reservierten Feldnummern verwenden.

Feldregeln festlegen

Es gibt folgende Meldungsfelder:

  • singular: Eine korrekt formatierte Nachricht kann null oder eines der Felder enthalten (jedoch nicht mehr als eines). Wenn Sie die Proto3-Syntax verwenden, ist dies die Standardfeldregel, wenn für ein bestimmtes Feld keine anderen Feldregeln angegeben sind. Sie können nicht feststellen, ob es vom Kabel geparst wurde. Er wird für das Kabel serialisiert, es sei denn, es ist der Standardwert. Weitere Informationen zu diesem Thema finden Sie unter Feldpräsenz.
  • optional: entspricht singular, kann aber geprüft werden, ob der Wert explizit festgelegt wurde. Das Feld optional kann einen von zwei möglichen Status haben:
    • das Feld festgelegt ist und einen Wert enthält, der explizit festgelegt oder von der Leitung geparst wurde. Sie wird an das Kabel angeschlossen.
    • Wenn das Feld nicht festgelegt ist, wird der Standardwert zurückgegeben. Es wird nicht zum Draht serialisiert.
  • repeated: Dieser Feldtyp kann in einer wohlgeformten Nachricht null- oder mehrmalig wiederholt werden. Die Reihenfolge der wiederholten Werte bleibt erhalten.
  • map: Dies ist ein Feldtyp mit einem Schlüssel/Wert-Paar. Weitere Informationen zu diesem Feldtyp finden Sie unter Maps.

In proto3 verwenden repeated-Felder skalarer numerischer Typen standardmäßig die packed-Codierung. Weitere Informationen zur packed-Codierung finden Sie unter Protokollpuffercodierung.

Weitere Nachrichtentypen hinzufügen

In einer .proto-Datei können mehrere Nachrichtentypen definiert werden. Dies ist nützlich, wenn Sie mehrere zusammengehörige Nachrichten definieren. Wenn Sie beispielsweise das Format für Antwortnachrichten definieren möchten, die Ihrem SearchResponse-Nachrichtentyp entsprechen, könnten Sie ihn demselben .proto hinzufügen:

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
}

message SearchResponse {
 ...
}

Kommentare hinzufügen

Verwenden Sie die Syntax C/C++-Stil // und /* ... */, um Kommentare zu Ihren .proto-Dateien hinzuzufügen.

/* SearchRequest represents a search query, with pagination options to
 * indicate which results to include in the response. */

message SearchRequest {
  string query = 1;
  int32 page_number = 2;  // Which page number do we want?
  int32 result_per_page = 3;  // Number of results to return per page.
}

Reservierte Felder

Wenn Sie einen Nachrichtentyp aktualisieren, indem Sie ein Feld vollständig entfernen oder auskommentieren, können zukünftige Nutzer die Feldnummer bei eigenen Aktualisierungen des Typs wiederverwenden. Dies kann zu schwerwiegenden Problemen führen, wenn später alte Versionen desselben .proto geladen werden, einschließlich Datenbeschädigung, Datenschutzfehlern usw. Eine Möglichkeit, dies zu vermeiden, besteht darin, die Feldnummern (und/oder Namen, die auch Probleme bei der JSON-Serialisierung verursachen können) Ihrer gelöschten Felder anzugeben: reserved. Der Protokollpufferkompilierer meldet sich, wenn zukünftige Nutzer versuchen, diese Feldkennungen zu verwenden.

message Foo {
  reserved 2, 15, 9 to 11;
  reserved "foo", "bar";
}

Beachten Sie, dass Sie in derselben reserved-Anweisung Feldnamen und Feldnummern nicht mischen können.

Was wird mit .proto generiert?

Wenn Sie den Protokollpufferkompilierer auf einem .proto ausführen, generiert der Compiler den Code in der ausgewählten Sprache. Sie müssen mit den Nachrichtentypen arbeiten, die Sie in der Datei beschrieben haben. Dazu gehören das Abrufen und Festlegen von Feldwerten, das Serialisieren Ihrer Nachrichten in einem Ausgabestream und das Parsen Ihrer Nachrichten aus einem Eingabestream.

  • Für C++ generiert der Compiler eine .h- und eine .cc-Datei aus jedem .proto mit einer Klasse für jeden in der Datei beschriebenen Nachrichtentyp.
  • Für Java generiert der Compiler eine .java-Datei mit einer Klasse für jeden Nachrichtentyp sowie eine spezielle Builder-Klasse zum Erstellen von Nachrichtenklasseninstanzen.
  • Für Kotlin generiert der Compiler zusätzlich zu dem von Java generierten Code für jeden Nachrichtentyp eine .kt-Datei mit einem DSL, der zur Erstellung von Nachrichteninstanzen verwendet werden kann.
  • Python ist ein wenig anders. Der Python-Compiler generiert ein Modul mit einem statischen Deskriptor jedes Nachrichtentyps in Ihrem .proto, der dann mit einer Metaklasse verwendet wird, um die erforderliche Python-Datenzugriffsklasse zur Laufzeit zu erstellen.
  • Für Go generiert der Compiler eine .pb.go-Datei mit einem Typ für jeden Nachrichtentyp in der Datei.
  • Für Ruby generiert der Compiler eine .rb-Datei mit einem Ruby-Modul, das Ihre Nachrichtentypen enthält.
  • Für Objective-C generiert der Compiler eine pbobjc.h- und eine pbobjc.m-Datei aus jedem .proto mit einer Klasse für jeden in der Datei beschriebenen Nachrichtentyp.
  • Für C# generiert der Compiler eine .cs-Datei aus jeder .proto-Datei mit einer Klasse für jeden in der Datei beschriebenen Nachrichtentyp.
  • Für Dart generiert der Compiler eine .pb.dart-Datei mit einer Klasse für jeden Nachrichtentyp in der Datei.

Weitere Informationen zur Verwendung der APIs für die einzelnen Sprachen finden Sie in der Anleitung für die ausgewählte Sprache (Proto3-Versionen demnächst verfügbar). Weitere Informationen zur API finden Sie in der entsprechenden API-Referenz (bald auch für Proto3-Versionen verfügbar).

Skalarwerttypen

Ein skalares Nachrichtenfeld kann einen der folgenden Typen haben. Die Tabelle zeigt den in der Datei .proto angegebenen Typ und den entsprechenden Typ in der automatisch generierten Klasse:

.proto-Typ Hinweise C++ Typ Java/Kotlin-Typ[1] Python-Typ[3] Go-Typ Ruby-Typ C#-Typ PHP-Typ Darttyp
double double double float Float64 Gleitkommazahl double float double
float float float float Float32 Gleitkommazahl float float double
int32 Verwendet Codierung mit variabler Länge. Ineffizient zur Codierung negativer Zahlen. Wenn das Feld wahrscheinlich negative Werte enthält, verwenden Sie stattdessen "intint32". int32 int int int32 Fixnum oder Bignum (nach Bedarf) int integer int
int64 Verwendet Codierung mit variabler Länge. Ineffizient zur Codierung negativer Zahlen. Wenn das Feld wahrscheinlich negative Werte enthält, verwenden Sie stattdessen sint64. int64 long int/long[4] int64 Bignum long Ganzzahl/String[6] INT64
Uint32 Verwendet Codierung mit variabler Länge. Uint32 int[2] int/long[4] Uint32 Fixnum oder Bignum (nach Bedarf) Uint integer int
Uint64 Verwendet Codierung mit variabler Länge. Uint64 Lang[2] int/long[4] Uint64 Bignum Ulong Ganzzahl/String[6] INT64
Sint32 Verwendet Codierung mit variabler Länge. Vorzeichenbehafteter Wert. Diese codieren negative Zahlen effizienter als normale int32s. int32 int int int32 Fixnum oder Bignum (nach Bedarf) int integer int
Sint64 Verwendet Codierung mit variabler Länge. Vorzeichenbehafteter Wert. Sie codieren negative Zahlen effizienter als normale int64-Werte. int64 long int/long[4] int64 Bignum long Ganzzahl/String[6] INT64
fest32 Immer vier Byte. Ist effizienter als uint32, wenn die Werte oft größer als 228 sind. Uint32 int[2] int/long[4] Uint32 Fixnum oder Bignum (nach Bedarf) Uint integer int
Fix64 Immer acht Byte. Ist effizienter als uint64, wenn Werte oft größer als 256 sind. Uint64 Lang[2] int/long[4] Uint64 Bignum Ulong Ganzzahl/String[6] INT64
feste32 Immer vier Byte. int32 int int int32 Fixnum oder Bignum (nach Bedarf) int integer int
Fixiert64 Immer acht Byte. int64 long int/long[4] int64 Bignum long Ganzzahl/String[6] INT64
bool bool boolean bool bool TrueClass/FalseClass bool boolean bool
String Ein String muss immer UTF-8-codiert oder 7-Bit-ASCII-Text enthalten und darf nicht länger als 232 sein. String String str/unicode[5]. String String (UTF-8) String String String
Byte Kann eine beliebige Abfolge von Byte enthalten, die nicht länger als 232 sind. String ByteString str (Python 2)
Byte (Python 3)
[]Byte String (ASCII-8BIT) ByteString String Liste

Weitere Informationen darüber, wie diese Typen codiert werden, finden Sie unter Protokollzwischenspeicher-Codierung.

[1] Kotlin verwendet die entsprechenden Java-Typen, auch für nicht signierte Typen, um die Kompatibilität mit gemischten Java/Kotlin-Codebasen zu gewährleisten.

[2] In Java werden nicht signierte 32-Bit- und 64-Bit-Ganzzahlen mithilfe ihrer signierten Gegenstücke dargestellt, wobei das obere Bit einfach im Vorzeichen-Bit gespeichert ist.

[3] Wenn Sie Werte für ein Feld festlegen, wird in allen Fällen die Typprüfung durchgeführt, um sicherzustellen, dass es gültig ist.

[4] 64-Bit- oder 32-Bit-Ganzzahlen ohne Vorzeichen werden bei der Decodierung immer als lang dargestellt, können aber beim Vorstellen des Felds eine Ganzzahl sein. In allen Fällen muss der Wert in den eingestellten Typ passen. Siehe [2].

[5] Python-Strings werden bei der Decodierung als Unicode dargestellt, können aber bei Verwendung eines ASCII-Strings durch str ersetzt werden (Änderungen vorbehalten).

[6] Ganzzahl wird auf 64-Bit-Maschinen und String auf 32-Bit-Maschinen verwendet.

Standardwerte

Wenn eine codierte Nachricht beim Parsen einer Nachricht kein bestimmtes Einzelnes Element enthält, wird das entsprechende Feld im geparsten Objekt auf den Standardwert für dieses Feld gesetzt. Diese Standardwerte sind typspezifisch:

  • Bei Strings ist der Standardwert der leere String.
  • Bei Byte ist der Standardwert leer.
  • Für boolesche Werte ist der Standardwert „false“.
  • Bei numerischen Typen ist der Standardwert null.
  • Für enums ist der Standardwert der erste Enum-Wert, der 0 sein muss.
  • Bei Nachrichtenfeldern ist das Feld nicht festgelegt. Der genaue Wert ist sprachabhängig. Weitere Informationen finden Sie im Leitfaden zu generiertem Code.

Der Standardwert für wiederkehrende Felder ist leer (im Allgemeinen eine leere Liste in der entsprechenden Sprache).

Bei skalaren Nachrichtenfeldern kann nach dem Parsen einer Nachricht nicht festgestellt werden, ob ein Feld explizit auf den Standardwert gesetzt wurde (z. B. ob ein boolescher Wert auf false festgelegt wurde) oder überhaupt nicht festgelegt wird: Dies sollten Sie beim Definieren der Nachrichtentypen berücksichtigen. Verwenden Sie beispielsweise keinen booleschen Wert, der ein bestimmtes Verhalten aktiviert, wenn false festgelegt ist, wenn Sie das nicht möchten. Wenn ein skalares Nachrichtenfeld auf Standard gesetzt ist, wird der Wert auf dem Draht nicht serialisiert.

Weitere Informationen zur Funktionsweise von Standardeinstellungen in generiertem Code finden Sie im Leitfaden zu generiertem Code für die gewählte Sprache.

Aufzählungen

Wenn Sie einen Nachrichtentyp definieren, kann es sein, dass eines seiner Felder nur einen der vordefinierten Werte haben soll. Beispiel: Sie möchten für jede SearchRequest ein Feld corpus hinzufügen. Der Korpus kann dabei UNIVERSAL, WEB, IMAGES, LOCAL, NEWS, PRODUCTS oder VIDEO sein. Dazu fügen Sie Ihrer Nachrichtendefinition einfach eine enum mit einer Konstante für jeden möglichen Wert hinzu.

Im folgenden Beispiel haben wir einen enum namens Corpus mit allen möglichen Werten und ein Feld vom Typ Corpus hinzugefügt:

enum Corpus {
  CORPUS_UNSPECIFIED = 0;
  CORPUS_UNIVERSAL = 1;
  CORPUS_WEB = 2;
  CORPUS_IMAGES = 3;
  CORPUS_LOCAL = 4;
  CORPUS_NEWS = 5;
  CORPUS_PRODUCTS = 6;
  CORPUS_VIDEO = 7;
}
message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
  Corpus corpus = 4;
}

Wie Sie sehen, wird die erste Konstante des Corpus-Enums Null zugeordnet. Jede Enum-Definition muss eine Konstante enthalten, die als erstes Element Null entspricht. Dies hat folgende Gründe:

  • Es muss null sein, damit wir 0 als numerischen Standardwert verwenden können.
  • Der Nullwert muss für die Kompatibilität mit der proto2-Semantik das erste Element sein, wobei der erste Enum-Wert immer der Standardwert ist.

Sie können Aliasse definieren, indem Sie verschiedenen Enum-Konstanten denselben Wert zuweisen. Setzen Sie dazu die Option allow_alias auf true. Andernfalls wird vom Protokoll-Compiler eine Fehlermeldung generiert, wenn Aliasse gefunden werden. Obwohl alle Aliaswerte während der Deserialisierung gültig sind, wird der erste Wert bei der Serialisierung immer verwendet.

enum EnumAllowingAlias {
  option allow_alias = true;
  EAA_UNSPECIFIED = 0;
  EAA_STARTED = 1;
  EAA_RUNNING = 1;
  EAA_FINISHED = 2;
}
enum EnumNotAllowingAlias {
  ENAA_UNSPECIFIED = 0;
  ENAA_STARTED = 1;
  // ENAA_RUNNING = 1;  // Uncommenting this line will cause a compile error inside Google and a warning message outside.
  ENAA_FINISHED = 2;
}

Zählerkonstanten müssen im Bereich einer 32-Bit-Ganzzahl liegen. Da enum-Werte die Varint-Codierung auf dem Draht verwenden, sind negative Werte ineffizient und werden daher nicht empfohlen. Sie können enums innerhalb einer Nachrichtendefinition wie im obigen Beispiel oder außerhalb definieren. Diese enums können in jeder Nachrichtendefinition in Ihrer .proto-Datei wiederverwendet werden. Sie können auch einen in einer Nachricht deklarierten enum-Typ als Feldtyp in einer anderen Nachricht mit der Syntax _MessageType_._EnumType_ verwenden.

Wenn Sie den Protokollpufferkompilierer auf einer .proto ausführen, der eine enum verwendet, hat der generierte Code eine entsprechende enum für Java, Kotlin oder C++ oder eine spezielle EnumDescriptor-Klasse für Python, mit der eine Reihe symbolischer Konstanten mit Ganzzahlwerten in der laufzeitgenerierten Klasse erstellt wird.

Während der Deserialisierung werden nicht erkannte Enum-Werte in der Nachricht beibehalten. Wie dies dargestellt wird, hängt jedoch von der deserialisierten Nachricht ab. In Sprachen, die offene Enum-Typen mit Werten außerhalb des Bereichs der angegebenen Symbole unterstützen, z. B. C++ und Go, wird der unbekannte Enum-Wert einfach als zugrunde liegende Ganzzahldarstellung gespeichert. In Sprachen mit geschlossenen Enum-Typen wie Java wird ein unbekannter Wert in der Aufzählung zur Darstellung eines unbekannten Werts verwendet. Auf die zugrunde liegende Ganzzahl kann mit speziellen Zugriffsmethoden zugegriffen werden. In beiden Fällen wird der nicht erkannte Wert nach wie vor mit der Nachricht serialisiert.

Weitere Informationen zur Verwendung von enum-Meldungen in Ihren Anwendungen finden Sie im Leitfaden zu generiertem Code für die gewählte Sprache.

Reservierte Werte

Wenn Sie einen Enum-Typ aktualisieren, indem Sie einen Enum-Eintrag vollständig entfernen oder auskommentieren, können zukünftige Nutzer den numerischen Wert wiederverwenden, wenn sie ihre eigenen Aktualisierungen des Typs vornehmen. Dies kann zu schwerwiegenden Problemen führen, wenn später alte Versionen desselben .proto geladen werden, einschließlich Datenbeschädigung, Datenschutzfehlern usw. Eine Möglichkeit, dies zu verhindern, besteht darin, die numerischen Werte (und/oder Namen, die auch Probleme bei der JSON-Serialisierung verursachen können) Ihrer gelöschten Einträge auf reserved festzulegen. Der Protokollpuffer-Compiler wird sich beschweren, wenn zukünftige Nutzer versuchen, diese Kennungen zu verwenden. Mit dem Schlüsselwort max können Sie angeben, dass Ihr reservierter numerischer Wertebereich den maximal möglichen Wert erhöht.

enum Foo {
  reserved 2, 15, 9 to 11, 40 to max;
  reserved "FOO", "BAR";
}

Beachten Sie, dass Sie in derselben reserved-Anweisung Feldnamen und numerische Werte nicht mischen können.

Andere Nachrichtentypen verwenden

Sie können andere Nachrichtentypen als Feldtypen verwenden. Angenommen, Sie möchten Result-Nachrichten in jede SearchResponse-Nachricht aufnehmen. Dazu können Sie einen Result-Nachrichtentyp im selben .proto definieren und dann in SearchResponse ein Feld vom Typ Result angeben:

message SearchResponse {
  repeated Result results = 1;
}

message Result {
  string url = 1;
  string title = 2;
  repeated string snippets = 3;
}

Definitionen importieren

Im obigen Beispiel ist der Nachrichtentyp Result in derselben Datei wie SearchResponse definiert. Was ist, wenn der Nachrichtentyp, den Sie als Feldtyp verwenden möchten, bereits in einer anderen .proto-Datei definiert ist?

Sie können Definitionen aus anderen .proto-Dateien importieren, um sie zu verwenden. Um die Definitionen eines anderen .protos zu importieren, fügen Sie oben in die Datei eine Importanweisung ein:

import "myproject/other_protos.proto";

Standardmäßig können Sie nur Definitionen aus direkt importierten .proto-Dateien verwenden. Gelegentlich kann es aber erforderlich sein, eine .proto-Datei an einen neuen Speicherort zu verschieben. Anstatt die Datei .proto direkt zu verschieben und alle Aufrufwebsites in einer einzigen Änderung zu aktualisieren, können Sie eine Platzhalterdatei .proto am alten Speicherort einfügen, um alle Importe mit dem Konzept import public an den neuen Speicherort weiterzuleiten.

Beachten Sie, dass der öffentliche Import in Java nicht verfügbar ist.

import public-Abhängigkeiten können vorübergehend von jedem Code verlassen werden, der die Proto-Datei mit der import public-Anweisung importiert. Beispiel:

// new.proto
// All definitions are moved here
// old.proto
// This is the proto that all clients are importing.
import public "new.proto";
import "other.proto";
// client.proto
import "old.proto";
// You use definitions from old.proto and new.proto, but not other.proto

Der Protokoll-Compiler sucht nach importierten Dateien in einer Reihe von Verzeichnissen, die in der Befehlszeile des Protokoll-Compilers mit dem Flag -I / --proto_path angegeben sind. Wenn kein Flag angegeben wurde, wird es in dem Verzeichnis gesucht, in dem der Compiler aufgerufen wurde. Im Allgemeinen sollten Sie das Flag --proto_path auf das Stammverzeichnis Ihres Projekts setzen und für alle Importe vollständig qualifizierte Namen verwenden.

Proto2-Nachrichtentypen verwenden

Sie können proto2-Nachrichtentypen importieren und in Ihren proto3-Nachrichten verwenden und umgekehrt. Proto2-Enums können jedoch nicht direkt in der Proto3-Syntax verwendet werden (es ist in Ordnung, wenn eine importierte Proto2-Nachricht diese verwendet).

Verschachtelte Typen

Sie können Nachrichtentypen innerhalb des folgenden Beispiels definieren und verwenden. Hier ist die Nachricht Result in der Nachricht SearchResponse definiert:

message SearchResponse {
  message Result {
    string url = 1;
    string title = 2;
    repeated string snippets = 3;
  }
  repeated Result results = 1;
}

Wenn Sie diesen Nachrichtentyp außerhalb des übergeordneten Nachrichtentyps wiederverwenden möchten, verweisen Sie auf ihn als _Parent_._Type_:

message SomeOtherMessage {
  SearchResponse.Result result = 1;
}

Sie können Nachrichten so tief verschachteln, wie Sie möchten:

message Outer {                  // Level 0
  message MiddleAA {  // Level 1
    message Inner {   // Level 2
      int64 ival = 1;
      bool  booly = 2;
    }
  }
  message MiddleBB {  // Level 1
    message Inner {   // Level 2
      int32 ival = 1;
      bool  booly = 2;
    }
  }
}

Nachrichtentyp aktualisieren

Wenn ein vorhandener Nachrichtentyp nicht mehr alle Ihre Anforderungen erfüllt – wenn Sie beispielsweise möchten, dass das Nachrichtenformat ein zusätzliches Feld hat –, aber Sie weiterhin den im alten Format erstellten Code verwenden möchten, ist das kein Problem. Nachrichtentypen lassen sich ganz einfach aktualisieren, ohne vorhandenen Code zu beschädigen. Beachten Sie dabei die folgenden Regeln:

  • Ändern Sie die Feldnummern vorhandener Felder nicht.
  • Wenn Sie neue Felder hinzufügen, können alle Nachrichten, die mithilfe des alten Nachrichtenformats serialisiert sind, mit dem neu generierten Code geparst werden. Beachten Sie die Standardwerte für diese Elemente, damit neuer Code mit Nachrichten interagieren kann, die von altem Code generiert wurden. Ebenso können Nachrichten, die von Ihrem neuen Code erstellt wurden, von Ihrem alten Code geparst werden: Alte Binärdateien ignorieren das neue Feld einfach beim Parsen. Weitere Informationen finden Sie im Abschnitt Unbekannte Felder.
  • Felder können entfernt werden, solange die Feldnummer in Ihrem aktualisierten Nachrichtentyp nicht noch einmal verwendet wird. Sie können das Feld stattdessen umbenennen und beispielsweise das Präfix „OBSOLETE_“ hinzufügen oder die Feldnummer reserviert machen, damit zukünftige Nutzer Ihres .proto die Nummer nicht versehentlich wiederverwenden können.
  • int32, uint32, int64, uint64 und bool sind kompatibel. Das bedeutet, dass Sie ein Feld von einem dieser Typen in einen anderen ändern können, ohne die Aufwärts- oder Abwärtskompatibilität zu beeinträchtigen. Wird eine Zahl von dem Draht geparst, der nicht in den entsprechenden Typ passt, hat dies denselben Effekt, als ob Sie die Nummer in C++ in diesen Typ umgewandelt hätten (z. B. wenn eine 64-Bit-Nummer als int32 gelesen wird, wird sie auf 32 Bits gekürzt).
  • sint32 und sint64 sind miteinander kompatibel, aber mit den anderen Ganzzahltypen nicht kompatibel.
  • string und bytes sind kompatibel, solange die Bytes eine gültige UTF-8-Codierung sind.
  • Eingebettete Nachrichten sind mit bytes kompatibel, wenn die Byte eine codierte Version der Nachricht enthalten.
  • fixed32 ist mit sfixed32 und fixed64 mit sfixed64 kompatibel.
  • Für string-, bytes- und Nachrichtenfelder sind einzelne Felder mit repeated-Feldern kompatibel. Bei serialisierten Daten eines wiederkehrenden Felds als Eingabe übernehmen Clients, die dieses Feld als Singular erwarten, den letzten Eingabewert, wenn es sich um ein einfaches Feld handelt, oder führen alle Eingabeelemente zusammen, wenn es sich um ein Nachrichtentypfeld handelt. Dies ist im Allgemeinen für numerische Typen, einschließlich boolescher Werte und Enums, nicht sicher. Wiederkehrende Felder numerischer Typen können im Format packed serialisiert werden. Dies wird nicht korrekt geparst, wenn ein einzelnes Feld erwartet wird.
  • enum ist in Bezug auf das Drahtformat mit int32, uint32, int64 und uint64 kompatibel. Die Werte werden gekürzt, wenn sie nicht passen. Beachten Sie jedoch, dass der Clientcode sie bei der Deserialisierung möglicherweise anders behandelt. Beispielsweise werden nicht erkannte Proto3-enum-Typen in der Nachricht beibehalten, aber wie sie dargestellt wird, wenn die Nachricht deserialisiert wird, hängt von der Sprache ab. Int-Felder behalten immer ihren Wert.
  • Das Ändern eines einzelnen optional-Felds oder einer Erweiterung in ein Mitglied eines neuen oneof ist binärkompatibel. Die API des generierten Codes wird jedoch für einige Sprachen (insbesondere Go) inkompatibel. Aus diesem Grund nimmt Google keine Änderungen an seinen öffentlichen APIs vor, wie in AIP-180 dokumentiert. Allerdings sollte das Verschieben mehrerer Felder in eine neue oneof sicher sein, wenn Sie sicher sind, dass kein Code mehr als ein Feld festlegt. Das Verschieben von Feldern in eine vorhandene oneof ist nicht sicher. Ebenso kann ein einzelnes oneof-Feld in ein optional-Feld oder eine Erweiterung geändert werden.

Unbekannte Felder

Unbekannte Felder sind serialisierte Protokollpufferdaten, die Felder darstellen, die vom Parser nicht erkannt werden. Wenn beispielsweise ein altes Binärprogramm Daten parst, die von einem neuen Binärprogramm mit neuen Feldern gesendet werden, werden diese neuen Felder in der alten Binärdatei zu unbekannten Feldern.

Ursprünglich haben proto3-Nachrichten beim Parsen immer unbekannte Felder verworfen. In Version 3.5 haben wir unbekannte Felder jedoch wiederhergestellt, um dem Proto2-Verhalten zu entsprechen. In Version 3.5 und höher werden unbekannte Felder während der Analyse beibehalten und in die serialisierte Ausgabe aufgenommen.

Alle

Mit dem Nachrichtentyp Any können Sie Nachrichten als eingebettete Typen ohne ihre .proto-Definition verwenden. Ein Any enthält eine beliebige serialisierte Nachricht als bytes sowie eine URL, die als global eindeutige Kennung für den Nachrichtentyp fungiert und in den Typ der Nachricht aufgelöst wird. Wenn Sie den Typ Any verwenden möchten, müssen Sie google/protobuf/any.proto importieren.

import "google/protobuf/any.proto";

message ErrorStatus {
  string message = 1;
  repeated google.protobuf.Any details = 2;
}

Die Standardtyp-URL für einen bestimmten Nachrichtentyp ist type.googleapis.com/_packagename_._messagename_.

Verschiedene Sprachimplementierungen unterstützen Helper für die Laufzeitbibliothek, um Any-Werte typsicher zu packen und zu entpacken. In Java hat der Typ Any beispielsweise spezielle pack()- und unpack()-Zugriffsfunktionen, während es in C++ die Methoden PackFrom() und UnpackTo() gibt:

// Storing an arbitrary message type in Any.
NetworkErrorDetails details = ...;
ErrorStatus status;
status.add_details()->PackFrom(details);

// Reading an arbitrary message from Any.
ErrorStatus status = ...;
for (const google::protobuf::Any& detail : status.details()) {
  if (detail.Is<NetworkErrorDetails>()) {
    NetworkErrorDetails network_error;
    detail.UnpackTo(&network_error);
    ... processing network_error ...
  }
}

Derzeit befinden sich die Laufzeitbibliotheken für die Arbeit mit Any-Typen noch in der Entwicklung.

Wenn Sie bereits mit der Proto2-Syntax vertraut sind, kann Any beliebige Proto3-Nachrichten enthalten, ähnlich wie Proto2-Nachrichten, die Erweiterungen zulassen.

Einer

Wenn Sie eine Nachricht mit vielen Feldern haben, in der höchstens ein Feld gleichzeitig festgelegt wird, können Sie dieses Verhalten erzwingen und Arbeitsspeicher mit dem Feature sparen.

Oneof-Felder sind wie reguläre Felder, außer allen Feldern in einem gemeinsamen Speicher. Es kann höchstens ein Feld gleichzeitig festgelegt werden. Wenn Sie eines der Mitglieder festlegen, werden automatisch auch alle anderen Mitglieder gelöscht. Mit einer speziellen case()- oder WhichOneof()-Methode können Sie prüfen, welcher Wert in einem der Werte festgelegt ist (je nach ausgewählter Sprache).

Wenn mehrere Werte festgelegt sind, überschreibt der letzte festgelegte Wert gemäß der Reihenfolge im Proto alle vorherigen Werte.

Oneof verwenden

Um ein Element in Ihrem .proto-Element zu definieren, verwenden Sie das Schlüsselwort oneof gefolgt von Ihrem Namen, in diesem Fall test_oneof:

message SampleMessage {
  oneof test_oneof {
    string name = 4;
    SubMessage sub_message = 9;
  }
}

Anschließend fügen Sie der Definition das Oneof-Feld hinzu. Sie können Felder eines beliebigen Typs hinzufügen. Ausgenommen sind die Felder map und repeated.

In dem generierten Code hat eines der Felder dieselben Getter und Setter wie reguläre Felder. Außerdem können Sie mit einer speziellen Methode prüfen, welcher Wert (falls vorhanden) festgelegt ist. Weitere Informationen zur Oneof API für die ausgewählte Sprache finden Sie in der entsprechenden API-Referenz.

Eine Funktion

  • Wenn Sie eines der Felder festlegen, werden alle anderen Mitglieder des Bereichs automatisch gelöscht. Wenn Sie also mehrere Felder festlegen, hat nur das letzte Feld einen Wert.

    SampleMessage message;
    message.set_name("name");
    CHECK_EQ(message.name(), "");
    // Calling mutable_sub_message() will clear the name field and will set
    // sub_message to a new instance of SubMessage with none of its fields set
    message.mutable_sub_message();
    CHECK(message.name().empty());
    
  • Wenn der Parser auf mehrere Mitglieder desselben Kabels stößt, wird nur das letzte Mitglied in der geparsten Nachricht verwendet.

  • Eines der folgenden Elemente ist nicht zulässig: repeated.

  • Reflexions-APIs funktionieren für eines der Felder.

  • Wenn Sie ein Feld „ofof“ auf den Standardwert setzen, z. B. ein int32 eines der Felder auf „0“, wird der „Fall“ dieses Feldes festgelegt und der Wert wird auf der Leitung serialisiert.

  • Wenn Sie C++ verwenden, achten Sie darauf, dass Ihr Code keine Arbeitsspeicherabstürze verursacht. Der folgende Beispielcode stürzt ab, da sub_message bereits durch Aufrufen der Methode set_name() gelöscht wurde.

    SampleMessage message;
    SubMessage* sub_message = message.mutable_sub_message();
    message.set_name("name");      // Will delete sub_message
    sub_message->set_...            // Crashes here
    
  • Auch hier gilt: Wenn Sie in C++ Swap() zwei Nachrichten mit einer der beiden Optionen haben, wird jede Nachricht am Ende der anderen Nachricht angezeigt: Im Beispiel unten hat msg1 eine sub_message und msg2 eine name.

    SampleMessage msg1;
    msg1.set_name("name");
    SampleMessage msg2;
    msg2.mutable_sub_message();
    msg1.swap(&msg2);
    CHECK(msg1.has_sub_message());
    CHECK_EQ(msg2.name(), "");
    

Probleme mit der Abwärtskompatibilität

Seien Sie vorsichtig, wenn Sie Felder hinzufügen oder entfernen. Wenn beim Prüfen des Werts eines Elements None/NOT_SET zurückgegeben wird, kann es sein, dass der Wert noch nicht festgelegt oder auf ein Feld in einer anderen Version gesetzt wurde. Es gibt keine Möglichkeit, den Unterschied zu unterscheiden, da nicht bekannt ist, ob ein unbekanntes Feld auf dem Draht Teil des Drahts ist.

Probleme mit der Wiederverwendung von Tags

  • Felder in eines oder aus einem der Elemente verschieben: Nachdem Sie die Nachricht serialisiert und geparst haben, können einige Ihrer Informationen verloren gehen (einige Felder werden gelöscht). Sie können aber ein einzelnes Feld sicher in ein neues Feld verschieben und mehrere Felder verschieben, wenn bekannt ist, dass nur eines davon festgelegt ist. Weitere Informationen finden Sie unter Nachrichtentyp aktualisieren.
  • Oneof-Feld löschen und wieder hinzufügen: Dadurch kann das aktuell festgelegte Feld gelöscht werden, nachdem die Nachricht serialisiert und geparst wurde.
  • Aufteilen oder Zusammenführen: Dies hat ähnliche Probleme wie das Verschieben regulärer Felder.

Maps

Wenn Sie eine assoziative Zuordnung als Teil Ihrer Datendefinition erstellen möchten, bieten Protokollpuffer eine praktische Verknüpfungssyntax:

map<key_type, value_type> map_field = N;

...wobei key_type ein ganzzahliger oder Stringtyp sein kann (also ein beliebiger skalarer Typ mit Ausnahme von Gleitkommatypen und bytes). Enum ist kein gültiger key_type. value_type kann ein beliebiger Typ sein, außer einer anderen Karte.

Wenn Sie beispielsweise eine Zuordnung von Projekten erstellen möchten, bei denen jede Project-Nachricht mit einem Stringschlüssel verknüpft ist, können Sie dies so definieren:

map<string, Project> projects = 3;

  • Kartenfelder dürfen nicht repeated sein.
  • Die Reihenfolge der Drahtformate und Karteniterationen von Kartenwerten ist nicht definiert. Daher können Sie sich nicht darauf verlassen, dass Ihre Kartenelemente in einer bestimmten Reihenfolge angeordnet sind.
  • Beim Generieren des Textformats für .proto werden Karten nach Schlüssel sortiert. Numerische Schlüssel werden numerisch sortiert.
  • Beim Parsen des Kabels oder beim Zusammenführen, wenn doppelte Zuordnungsschlüssel vorhanden sind, wird der zuletzt erkannte Schlüssel verwendet. Beim Parsen einer Karte aus dem Textformat kann das Parsen fehlschlagen, wenn doppelte Schlüssel vorhanden sind.
  • Wenn Sie einen Schlüssel, aber keinen Wert für ein Zuordnungsfeld angeben, ist das Verhalten bei der Serialisierung des Felds sprachabhängig. In C++, Java, Kotlin und Python ist der Standardwert für den Typ serialisiert, in anderen Sprachen ist jedoch nichts serialisiert.

Die generierte Maps API ist derzeit für alle von Proto3 unterstützten Sprachen verfügbar. Weitere Informationen zur Maps API für die ausgewählte Sprache finden Sie in der entsprechenden API-Referenz.

Abwärtskompatibilität

Die Kartensyntax entspricht der folgenden auf dem Kabel, sodass Protokollpuffer-Implementierungen, die keine Karten unterstützen, Ihre Daten trotzdem verarbeiten können:

message MapFieldEntry {
  key_type key = 1;
  value_type value = 2;
}

repeated MapFieldEntry map_field = N;

Jede Protokollpufferimplementierung, die Karten unterstützt, muss Daten erzeugen und akzeptieren, die von der obigen Definition akzeptiert werden können.

Pakete

Sie können einer .proto-Datei einen optionalen Spezifizierer package hinzufügen, um Namenskonflikte zwischen Protokollnachrichtentypen zu vermeiden.

package foo.bar;
message Open { ... }

Sie können dann den Paketbezeichner verwenden, um Felder des Nachrichtentyps zu definieren:

message Foo {
  ...
  foo.bar.Open open = 1;
  ...
}

Wie sich ein Paketbezeichner auf den generierten Code auswirkt, hängt von der ausgewählten Sprache ab:

  • In C++ sind die generierten Klassen in einen C++-Namespace eingeschlossen. So würde sich Open beispielsweise im Namespace foo::bar befinden.
  • In Java und Kotlin wird das Paket als Java-Paket verwendet, sofern Sie in der Datei .proto nicht explizit ein option java_package angeben.
  • In Python wird die Paketanweisung ignoriert, da Python-Module nach ihrem Speicherort im Dateisystem organisiert sind.
  • In Go wird das Paket als Go-Paketname verwendet, sofern Sie in der Datei .proto nicht explizit eine option go_package angeben.
  • In Ruby sind die generierten Klassen in verschachtelten Ruby-Namespaces zusammengefasst und in den erforderlichen Ruby-Großschreibungsstil konvertiert. Der erste Buchstabe wird großgeschrieben. Wenn das erste Zeichen kein Buchstabe ist, wird PB_ vorbereitet. Open wäre beispielsweise im Namespace Foo::Bar enthalten.
  • In C# wird das Paket nach der Konvertierung in PascalCase als Namespace verwendet, sofern Sie in der Datei .proto nicht explizit eine option csharp_namespace angeben. Open wäre beispielsweise im Namespace Foo.Bar enthalten.

Pakete und Namensauflösung

Die Auflösung des Typnamens in der Protokollpuffersprache funktioniert wie C++: zuerst wird der innerste Bereich durchsucht, dann der nächste innerste usw., wobei jedes Paket als „inner“ für das übergeordnete Paket gilt. Ein vorangestelltes „.“ (z. B. .foo.bar.Baz) beginnt stattdessen mit dem äußersten Bereich.

Der Protokollpuffer-Compiler löst alle Typnamen auf, indem er die importierten .proto-Dateien parst. Der Codegenerator für jede Sprache weiß, wie er sich auf jeden Typ in dieser Sprache beziehen kann, auch wenn er unterschiedliche Zuordnungsregeln hat.

Dienste definieren

Wenn Sie Ihre Nachrichtentypen mit einem RPC-System (Remote Procedure Call) verwenden möchten, können Sie eine RPC-Dienstschnittstelle in einer .proto-Datei definieren. Der Protokollpufferkompilierer generiert dann Dienstschnittstellencode und Stubs in der ausgewählten Sprache. Wenn Sie also beispielsweise einen RPC-Dienst mit einer Methode definieren möchten, die SearchRequest annimmt und ein SearchResponse-Objekt zurückgibt, können Sie dies so in der .proto-Datei definieren:

service SearchService {
  rpc Search(SearchRequest) returns (SearchResponse);
}

Das einfachste RPC-System, das mit Protokollpuffern verwendet wird, ist gRPC, ein sprach- und plattformneutrales Open-Source-RPC-System, das bei Google entwickelt wurde. gRPC funktioniert besonders gut mit Protokollpuffern. Mit einem speziellen Protokollpuffer-Compiler-Plug-in können Sie den relevanten RPC-Code direkt aus Ihren .proto-Dateien generieren.

Wenn Sie gRPC nicht verwenden möchten, können Sie auch Protokollpuffer mit Ihrer eigenen RPC-Implementierung verwenden. Weitere Informationen hierzu finden Sie im Proto2-Sprachleitfaden.

Außerdem gibt es eine Reihe von laufenden Projekten von Drittanbietern zur Entwicklung von RPC-Implementierungen für Protocol Buffers. Eine Liste mit Links zu Projekten, die uns bekannt sind, finden Sie auf der Wiki-Seite mit Add-ons von Drittanbietern.

JSON-Zuordnung

Proto3 unterstützt eine kanonische Codierung in JSON, wodurch die Datenfreigabe zwischen Systemen vereinfacht wird. Die Codierung wird in der folgenden Tabelle für die einzelnen Typen beschrieben.

Wenn JSON-codierte Daten in einen Protokollpuffer geparst werden und ein Wert fehlt oder der Wert null ist, wird er als der entsprechende Standardwert interpretiert.

Wenn eine JSON-codierte Ausgabe aus einem Protokollpuffer generiert wird und ein Protokollpuffer-Feld den Standardwert hat und das Feld die Feldpräsenz nicht unterstützt, wird es standardmäßig aus der Ausgabe ausgelassen. Eine Implementierung kann Optionen zum Einschließen von Feldern mit Standardwerten in die Ausgabe enthalten.

Ein Proto3-Feld, das mit dem Schlüsselwort optional definiert ist, unterstützt die Feldpräsenz. Felder, für die ein Wert festgelegt ist und die die Feldpräsenz unterstützen, enthalten den Feldwert immer in der JSON-codierten Ausgabe, auch wenn es sich um den Standardwert handelt.

proto3 JSON JSON-Beispiel Hinweise
Nachricht Objekt {"fooBar": v, "g": null, …} Generiert JSON-Objekte. Namen von Nachrichtenfeldern werden LowCamelCase zugeordnet und werden zu JSON-Objektschlüsseln. Wenn die Feldoption json_name angegeben ist, wird stattdessen der angegebene Wert als Schlüssel verwendet. Parser akzeptieren sowohl den Namen „CamelCase“ (oder den in der Option json_name angegebenen Namen) als auch den ursprünglichen Proto-Feldnamen. null ist ein zulässiger Wert für alle Feldtypen und wird als Standardwert des entsprechenden Feldtyps behandelt.
enum String "FOO_BAR" Der in proto angegebene Name des Enum-Werts wird verwendet. Parser akzeptieren sowohl Enum-Namen als auch Ganzzahlwerte.
map<K,V> Objekt {"k": v, …} Alle Schlüssel werden in Strings konvertiert.
wiederholtes V array [v, …] null wird als leere Liste [] akzeptiert.
bool true, false true, false
String String "Hello World!"
Byte base64-String "YWJjMTIzIT8kKiYoKSctPUB+" Der JSON-Wert entspricht den Daten, die als String mit Standard-Base64-Codierung und Padding codiert sind. Es wird entweder eine Standard- oder URL-sichere Base64-Codierung mit/ohne Padding akzeptiert.
int32, fest32, uint32 Zahl 1, -10, 0 Der JSON-Wert ist eine Dezimalzahl. Es werden entweder Zahlen oder Strings akzeptiert.
int64, feste 64, uint64 String "1", "-10" Der JSON-Wert ist ein Dezimalstring. Es werden entweder Zahlen oder Strings akzeptiert.
Gleitkommazahl, doppelt Zahl 1.1, -10.0, 0, "NaN", "Infinity" Der JSON-Wert ist eine Zahl oder einer der speziellen Stringwerte „NaN“, „Infinity“ und „-Infinity“. Es werden entweder Zahlen oder Strings akzeptiert. Die Exponentenschreibweise wird ebenfalls akzeptiert. -0 gilt als gleichwertig mit 0.
Alle object {"@type": "url", "f": v, … } Wenn Any einen Wert mit einer speziellen JSON-Zuordnung enthält, wird er so konvertiert: {"@type": xxx, "value": yyy}. Andernfalls wird der Wert in ein JSON-Objekt umgewandelt und das Feld "@type" eingefügt, um den tatsächlichen Datentyp anzugeben.
Zeitstempel String "1972-01-01T10:00:20.021Z" Verwendet RFC 3339, wobei die generierte Ausgabe immer Z-normalisiert ist und 0, 3, 6 oder 9 Nachkommastellen verwendet werden. Andere Werte als „Z“ sind zulässig.
Dauer String "1.000340012s", "1s" Die generierte Ausgabe enthält je nach erforderlicher Genauigkeit immer 0, 3, 6 oder 9 Nachkommastellen, gefolgt vom Suffix „s“. Zulässig sind Bruchziffern (ebenfalls keine), die bis auf Nanosekunden genau passen und das Suffix "s" erforderlich ist.
Struct object { … } Jedes JSON-Objekt. Weitere Informationen finden Sie in den struct.proto.
Wrapper-Typen verschiedene Typen 2, "2", "foo", true, "true", null, 0, … Wrapper verwenden in JSON dieselbe Darstellung wie der umschlossene primitive Typ, mit der Ausnahme, dass null während der Datenkonvertierung und -übertragung zulässig ist und beibehalten wird.
FieldMask String "f.fooBar,h" Weitere Informationen finden Sie in den field_mask.proto.
ListValue array [foo, bar, …]
Wert Wert Beliebiger JSON-Wert. Weitere Informationen finden Sie unter google. aufgeführt.
NullValue null JSON-Null
Leer Objekt {} Ein leeres JSON-Objekt

JSON-Optionen

Eine Proto3-JSON-Implementierung kann folgende Optionen bieten:

  • Ausgabefelder mit Standardwerten: Felder mit Standardwerten werden in der Proto3-JSON-Ausgabe standardmäßig weggelassen. Eine Implementierung bietet möglicherweise die Möglichkeit, dieses Verhalten und Ausgabefelder mit ihren Standardwerten zu überschreiben.
  • Unbekannte Felder ignorieren: Der Proto3-JSON-Parser sollte unbekannte Felder standardmäßig ablehnen. Sie haben aber die Möglichkeit, unbekannte Felder beim Parsen zu ignorieren.
  • Proto-Feldname anstelle von "CamelCase" mit niedrigerem Namen verwenden: Der Proto3-JSON-Drucker sollte den Feldnamen standardmäßig in LowCamelCase umwandeln und als JSON-Namen verwenden. Eine Implementierung bietet die Möglichkeit, stattdessen den Namen des Proto-Felds als JSON-Namen zu verwenden. Proto3-JSON-Parser sind erforderlich, um sowohl den konvertierten „CamelCase“-Namen als auch den Namen des Proto-Felds zu akzeptieren.
  • Enum-Werte als Ganzzahlen statt als Strings ausgeben: Der Name eines Enum-Werts wird in der JSON-Ausgabe standardmäßig verwendet. Sie können stattdessen auch den numerischen Wert des Enum-Werts verwenden.

Optionen

Einzelne Deklarationen in einer .proto-Datei können mit einer Reihe von Optionen annotiert werden. Optionen haben keine Auswirkungen auf die allgemeine Bedeutung einer Deklaration, können sich jedoch auf die Verarbeitung in einem bestimmten Kontext auswirken. Die vollständige Liste der verfügbaren Optionen finden Sie in /google/protobuf/descriptor.proto.

Einige Optionen sind Optionen auf Dateiebene, d. h., sie sollten auf oberster Ebene geschrieben werden, nicht innerhalb einer Nachricht, Enum oder Dienstdefinition. Einige Optionen sind Optionen auf Nachrichtenebene. Sie sollten daher in Nachrichtendefinitionen geschrieben werden. Einige Optionen sind Optionen auf Feldebene. Sie sollten daher in Felddefinitionen geschrieben werden. Optionen können auch für Enum-Typen, Enum-Werte, Feld-, Dienst- und Dienstmethoden geschrieben werden. Derzeit gibt es jedoch für diese keine nützlichen Optionen.

Hier sind einige der am häufigsten verwendeten Optionen:

  • java_package (Dateioption): Das Paket, das Sie für die generierten Java/Kotlin-Klassen verwenden möchten. Wenn in der Datei .proto keine explizite java_package-Option angegeben ist, wird standardmäßig das Proto-Paket verwendet, das mit dem Schlüsselwort "package" in der Datei .proto angegeben wird. Proto-Pakete sind jedoch im Allgemeinen keine guten Java-Pakete, da davon auszugehen ist, dass sie nicht mit umgekehrten Domainnamen beginnen. Wenn kein Java- oder Kotlin-Code generiert wird, hat diese Option keine Auswirkung.

    option java_package = "com.example.foo";
    
  • java_outer_classname (Dateioption): Der Klassenname (und damit der Dateiname) für die Wrapper-Java-Klasse, die Sie generieren möchten. Wenn in der Datei .proto kein explizites java_outer_classname angegeben ist, wird der Klassenname durch Konvertieren des .proto-Dateinamens in "CamelCase" (foo_bar.proto wird zu FooBar.java) erstellt. Wenn die Option java_multiple_files deaktiviert ist, werden alle anderen Klassen/Enums/usw. für die Datei .proto in diesem äußeren Wrapper-Java-Kurs als verschachtelte Klassen/Enums/usw. generiert. Wenn Sie keinen Java-Code generieren, hat diese Option keine Auswirkung.

    option java_outer_classname = "Ponycopter";
    
  • java_multiple_files: Wenn kein Java-Code generiert wird, hat diese Option keine Auswirkung.

    option java_multiple_files = true;
    
  • optimize_for (Dateioption): Kann auf SPEED, CODE_SIZE oder LITE_RUNTIME festgelegt werden. Dies wirkt sich auf die C++- und Java-Code-Generatoren (und möglicherweise Drittanbieter-Generatoren) so aus:

    • SPEED (Standard): Der Protokollpufferkompilierer generiert Code zum Serialisieren, Parsen und Ausführen anderer gängiger Vorgänge für Ihre Nachrichtentypen. Dieser Code ist stark optimiert.
    • CODE_SIZE: Der Protokollpuffer-Compiler generiert nur wenige Klassen und verwendet einen reflexionsbasierten Code, um Serialisierung, Parsing und verschiedene andere Vorgänge zu implementieren. Der generierte Code ist daher viel kleiner als bei SPEED, aber die Vorgänge sind langsamer. Klassen implementieren weiterhin genau dieselbe öffentliche API wie im SPEED-Modus. Dieser Modus ist in Anwendungen mit einer sehr großen Anzahl von .proto-Dateien am besten geeignet, da nicht alle Dateien blind blinken müssen.
    • LITE_RUNTIME: Der Protokollpufferkompilierer generiert Klassen, die nur von der Lite-Laufzeitbibliothek (libprotobuf-lite statt libprotobuf) abhängig sind. Die Lite-Laufzeit ist viel kleiner als die vollständige Bibliothek (etwa kleiner). Sie enthält jedoch bestimmte Merkmale wie Deskriptoren und Reflexion. Dies ist besonders nützlich für Apps, die auf eingeschränkten Plattformen wie Smartphones ausgeführt werden. Der Compiler generiert weiterhin schnelle Implementierungen aller Methoden wie im Modus SPEED. Die generierten Klassen implementieren nur die MessageLite-Schnittstelle in jeder Sprache, die nur einen Teil der Methoden der vollständigen Message-Schnittstelle bietet.
    option optimize_for = CODE_SIZE;
    
  • cc_enable_arenas (Dateioption): Aktiviert die Arena-Zuordnung für den mit C++ generierten Code.

  • objc_class_prefix (Dateioption): Legt das Präfix der Objective-C-Klasse fest, das allen von Objective-C generierten Klassen und Enums aus diesem Proto vorangestellt wird. Es gibt keine Standardeinstellung. Verwenden Sie Präfixe zwischen 3 und 5 Kleinbuchstaben (von Apple empfohlen). Alle Präfixe mit zwei Buchstaben sind Apple vorbehalten.

  • deprecated (Feldoption): Wenn dieses Feld auf true gesetzt ist, bedeutet das, dass das Feld veraltet ist und nicht im neuen Code verwendet werden soll. In den meisten Sprachen hat dies keine Auswirkungen. In Java wird dies eine @Deprecated-Annotation. Für C++ generiert clang-tidy Warnungen, wenn veraltete Felder verwendet werden. Zukünftig generieren andere sprachspezifische Codegeneratoren möglicherweise veraltete Annotationen über die Zugriffsfunktionen des Felds. Dies führt wiederum dazu, dass beim Kompilieren von Code, der versucht, das Feld zu verwenden, eine Warnung ausgegeben wird. Wenn das Feld von niemandem verwendet wird und Sie verhindern möchten, dass neue Nutzer es verwenden, sollten Sie die Felddeklaration durch eine reservierte Anweisung ersetzen.

    int32 old_field = 6 [deprecated = true];
    

Personalisierte Optionen

Mit Protocol Buffers können Sie außerdem Ihre eigenen Optionen definieren und verwenden. Dies ist eine erweiterte Funktion, die die meisten Nutzer nicht benötigen. Wenn Sie Ihrer Meinung nach eigene Optionen erstellen müssen, finden Sie entsprechende Informationen im Proto2-Sprachleitfaden. Für benutzerdefinierte Optionen werden Erweiterungen verwendet, die nur für benutzerdefinierte Optionen in proto3 zulässig sind.

Kurse erstellen

Zum Generieren des Java-, Kotlin-, Python-, C ++-, Go-, Ruby-, Objective-C- oder C#-Codes, der mit den in einer .proto-Datei definierten Nachrichtentypen funktioniert, müssen Sie den Protokollpuffer-Compiler protoc auf .proto ausführen. Wenn Sie den Compiler nicht installiert haben, laden Sie das Paket herunter und folgen Sie der Anleitung in der README-Datei. Für Go müssen Sie außerdem ein spezielles Code-Generator-Plug-in für den Compiler installieren. Sie finden diese und die Installationsanleitung im Repository golang/Proto auf GitHub.

Der Protokoll-Compiler wird so aufgerufen:

protoc --proto_path=IMPORT_PATH --cpp_out=DST_DIR --java_out=DST_DIR --python_out=DST_DIR --go_out=DST_DIR --ruby_out=DST_DIR --objc_out=DST_DIR --csharp_out=DST_DIR path/to/file.proto
  • IMPORT_PATH gibt ein Verzeichnis an, in dem nach import-Anweisungen nach .proto-Dateien gesucht werden soll. Wenn nichts angegeben ist, wird das aktuelle Verzeichnis verwendet. Sie können mehrere Importverzeichnisse angeben, indem Sie die Option --proto_path mehrmals übergeben. Sie werden in der angegebenen Reihenfolge durchsucht. -I=_IMPORT_PATH_ kann als Kurzform von --proto_path verwendet werden.
  • Sie können eine oder mehrere Ausgabeanweisungen angeben:

    Wenn DST_DIR auf .zip oder .jar endet, schreibt der Compiler die Ausgabe zusätzlich in eine einzelne Archivdatei im ZIP-Format mit dem angegebenen Namen. .jar-Ausgaben erhalten entsprechend der Java-JAR-Spezifikation auch eine Manifestdatei. Wenn das Ausgabearchiv bereits vorhanden ist, wird es überschrieben. Der Compiler ist nicht intelligent genug, um einem vorhandenen Archiv Dateien hinzuzufügen.

  • Sie müssen eine oder mehrere .proto-Dateien als Eingabe angeben. Es können mehrere .proto-Dateien gleichzeitig angegeben werden. Obwohl die Dateien relativ zum aktuellen Verzeichnis benannt sind, muss sich jede Datei in einem der IMPORT_PATHs befinden, damit der Compiler seinen kanonischen Namen ermitteln kann.

Dateispeicherort

Speichern Sie .proto-Dateien lieber nicht im selben Verzeichnis wie andere Sprachquellen. Sie können für das Projekt ein Unterpaket proto für .proto-Dateien erstellen.

Der Standort sollte sprachunabhängig sein

Bei der Arbeit mit Java-Code empfiehlt es sich, zugehörige .proto-Dateien im selben Verzeichnis wie die Java-Quelle zu speichern. Wenn jedoch kein Java-Code die gleichen Proto-Dateien verwendet, ist das Pfadpräfix nicht mehr sinnvoll. Platzieren Sie die Proto-Dateien im Allgemeinen in einem zugehörigen sprachunabhängigen Verzeichnis wie //myteam/mypackage.

Eine Ausnahme von dieser Regel gibt es, wenn die Proto-Dateien nur in einem Java-Kontext verwendet werden, z. B. zu Testzwecken.

Unterstützte Plattformen

Informationen zu folgenden Themen: