So definieren Sie ein optionales Feld in protobuf 3

103

Ich muss eine Nachricht mit einem optionalen Feld in protobuf (proto3-Syntax) angeben. In Bezug auf die Proto 2-Syntax ist die Nachricht, die ich ausdrücken möchte, ungefähr so:

message Foo {
    required int32 bar = 1;
    optional int32 baz = 2;
}

Nach meinem Verständnis wurde das "optionale" Konzept aus Syntax Proto 3 entfernt (zusammen mit dem erforderlichen Konzept). Obwohl die Alternative nicht klar ist: Wenn Sie den Standardwert verwenden, um anzugeben, dass kein Feld vom Absender angegeben wurde, bleibt eine Mehrdeutigkeit, wenn der Standardwert zur Domäne der gültigen Werte gehört (z. B. ein boolescher Typ).

Wie soll ich die obige Nachricht verschlüsseln? Vielen Dank.

MaxP
quelle
Ist der unten stehende Ansatz eine gute Lösung? Nachricht NoBaz {} Nachricht Foo {int32 bar = 1; oneof baz {NoBaz undefined = 2; int32 defined = 3; }; }
MaxP
2
Es gibt eine Proto 2-Version dieser Frage , wenn andere dies finden, aber Proto 2 verwenden.
chwarr
1
proto3 macht grundsätzlich alle Felder optional. Bei Skalaren war es jedoch unmöglich, zwischen "Feld nicht gesetzt" und "Feld gesetzt, aber auf Standardwert" zu unterscheiden. Wenn Sie Ihren Skalar in einen Singleton einwickeln, z. B. - message blah {oneof v1 {int32 foo = 1; }}, dann können Sie erneut überprüfen, ob foo tatsächlich eingestellt wurde oder nicht. Zumindest für Python können Sie direkt mit foo arbeiten, als ob es nicht in einem von einem wäre, und Sie können HasField ("foo") fragen.
jschultz410
1
@MaxP Vielleicht können Sie die akzeptierte Antwort in stackoverflow.com/a/62566052/66465 ändern, da jetzt eine neuere Version von protobuf 3 verfügbar istoptional
SebastianK

Antworten:

39

Seit Protobuf Release 3.12 unterstützt proto3 die Verwendung des optionalSchlüsselworts (genau wie in proto2), um Informationen zum Vorhandensein eines Skalarfelds zu erhalten.

syntax = "proto3";

message Foo {
    int32 bar = 1;
    optional int32 baz = 2;
}

Für das obige Feld wird eine has_baz()/ hasBaz()-Methode generiert optional, genau wie in proto2.

Unter der Haube behandelt protoc ein optionalFeld effektiv so , als ob es mit einem oneofWrapper deklariert worden wäre , wie die Antwort von CyberSnoopy nahe legt:

message Foo {
    int32 bar = 1;
    oneof optional_baz {
        int32 baz = 2;
    }
}

Wenn Sie diesen Ansatz bereits verwendet haben, können Sie Ihre Nachrichtendeklarationen bereinigen (von oneofzu wechseln optional), da das Drahtformat dasselbe ist.

Die optionalwichtigsten Details zur Feldpräsenz und in proto3 finden Sie im Anwendungshinweis: Feldpräsenzdokument .

In Version 3.12 erfordert diese Funktionalität die Übergabe des --experimental_allow_proto3_optionalFlags an protoc. Die Feature-Ankündigung besagt , dass es "hoffentlich in 3.13 allgemein verfügbar sein wird".

Update Oktober 2020: Die Funktion wird in Version 3.13 weiterhin als experimentell (Flag erforderlich) betrachtet .

Jaredjacobs
quelle
1
Wissen Sie zufällig, wie man die Flagge in C # übergibt?
James Hancock
3
Dies muss positiv bewertet werden.
Lucas
Dies ist die beste Antwort, nachdem proto3 eine bessere Syntax hinzugefügt hat. Toller Callout Jarad!
Evan Moran
Nur zum Hinzufügen für optional int xyz: 1) has_xyzerkennt, ob der optionale Wert eingestellt wurde 2) setzt clear_xyzden Wert zurück. Weitere Infos hier: github.com/protocolbuffers/protobuf/blob/master/docs/…
Evan Moran
@ JamesHancock oder Java?
Tobi Akinyemi vor
123

In proto3 sind alle Felder "optional" (insofern ist es kein Fehler, wenn der Absender sie nicht festlegt). Felder sind jedoch nicht mehr "nullbar", da es keinen Unterschied zwischen einem Feld gibt, das explizit auf seinen Standardwert gesetzt wurde, und einem Feld, das überhaupt nicht gesetzt wurde.

Wenn Sie einen "Null" -Status benötigen (und es keinen Wert außerhalb des Bereichs gibt, den Sie dafür verwenden können), müssen Sie diesen stattdessen als separates Feld codieren. Zum Beispiel könnten Sie tun:

message Foo {
  bool has_baz = 1;  // always set this to "true" when using baz
  int32 baz = 2;
}

Alternativ können Sie Folgendes verwenden oneof:

message Foo {
  oneof baz {
    bool baz_null = 1;  // always set this to "true" when null
    int32 baz_value = 2;
  }
}

Die oneofVersion ist expliziter und effizienter, erfordert jedoch Verständnis für die Funktionsweise von oneofWerten.

Schließlich ist eine andere völlig vernünftige Option, bei proto2 zu bleiben. Proto2 ist nicht veraltet, und tatsächlich hängen viele Projekte (einschließlich innerhalb von Google) sehr stark von Proto2-Funktionen ab, die in Proto3 entfernt wurden, daher werden sie wahrscheinlich nie wechseln. Es ist also sicher, es auf absehbare Zeit weiter zu verwenden.

Kenton Varda
quelle
Ähnlich wie bei Ihrer Lösung habe ich in meinem Kommentar vorgeschlagen, den einen mit dem realen Wert und einem Nulltyp (eine leere Nachricht) zu verwenden. Auf diese Weise stören Sie sich nicht am booleschen Wert (der nicht relevant sein sollte, denn wenn es den booleschen Wert gibt, gibt es keinen baz_value) Richtig?
MaxP
2
@MaxP Ihre Lösung funktioniert, aber ich würde einen Booleschen Wert anstelle einer leeren Nachricht empfehlen. In beiden Fällen werden zwei Bytes auf dem Kabel benötigt, aber für die leere Nachricht werden erheblich mehr CPU, RAM und generierter Code aufgebläht.
Kenton Varda
13
Ich finde die Nachricht Foo {oneof baz {int32 baz_value = 1; }} funktioniert ziemlich gut.
CyberSnoopy
@CyberSnoopy Kannst du es als Antwort posten? Ihre Lösung funktioniert perfekt und elegant.
Cheng Chen
@CyberSnoopy Haben Sie zufällig Probleme beim Senden einer Antwortnachricht festgestellt, die wie folgt strukturiert ist: message FooList {wiederholte Foo foos = 1; }? Ihre Lösung ist großartig, aber ich habe jetzt Probleme, FooList als Serverantwort zu senden.
CaffeinateOften
95

Eine Möglichkeit besteht darin, oneofwie in der akzeptierten Antwort vorgeschlagen zu verwenden.

Eine andere Möglichkeit besteht darin, Wrapper-Objekte zu verwenden. Sie müssen sie nicht selbst schreiben, da Google sie bereits bereitstellt:

Fügen Sie oben in Ihrer .proto-Datei diesen Import hinzu:

import "google/protobuf/wrappers.proto";

Jetzt können Sie für jeden einfachen Typ spezielle Wrapper verwenden:

DoubleValue
FloatValue
Int64Value
UInt64Value
Int32Value
UInt32Value
BoolValue
StringValue
BytesValue

Um die ursprüngliche Frage zu beantworten, könnte die Verwendung eines solchen Wrappers folgendermaßen aussehen:

message Foo {
    int32 bar = 1;
    google.protobuf.Int32Value baz = 2;
}

Jetzt kann ich zum Beispiel in Java Dinge tun wie:

if(foo.hasBaz()) { ... }

VM4
quelle
3
Wie funktioniert das? Wenn baz=nullund wann baznicht übergeben wird, in beiden Fällen hasBaz()sagt false!
Mayankcpdixit
Die Idee ist einfach: Sie verwenden Wrapper-Objekte oder mit anderen Worten benutzerdefinierte Typen. Diese Wrapper-Objekte dürfen fehlen. Das von mir bereitgestellte Java-Beispiel hat bei der Arbeit mit gRPC gut funktioniert.
VM4
Ja! Ich verstehe die allgemeine Idee, aber ich wollte sie in Aktion sehen. Was ich nicht verstehe ist: (sogar im Wrapper-Objekt) " Wie man fehlende und null Wrapper-Werte identifiziert? "
mayankcpdixit
2
Dies ist der richtige Weg. Mit C # erzeugt der generierte Code nullable <T> -Eigenschaften.
Aaron Hudon
5
Besser als der ursprüngliche Awsner!
Dev Aggarwal
31

Basierend auf Kentons Antwort sieht eine einfachere und dennoch funktionierende Lösung so aus:

message Foo {
    oneof optional_baz { // "optional_" prefix here just serves as an indicator, not keyword in proto2
        int32 baz = 1;
    }
}
CyberSnoopy
quelle
Wie verkörpert dies das optionale Zeichen?
JFFIGK
18
Grundsätzlich ist einer von ihnen schlecht benannt. Es bedeutet "höchstens eines von". Es gibt immer einen möglichen Nullwert.
Ecl3ctic
Wenn nicht gesetzt, lautet der Wert case None(in C #). Die Sprache Ihrer Wahl finden Sie im Aufzählungstyp.
Nitzel
Ja, dies ist wahrscheinlich der beste Weg, um diese Katze in proto3 zu häuten - auch wenn es die .proto ein bisschen hässlich macht.
jschultz410
Dies bedeutet jedoch, dass Sie das Fehlen eines Felds so interpretieren können, dass es explizit auf den Nullwert gesetzt wird. Mit anderen Worten, es gibt eine gewisse Mehrdeutigkeit zwischen "optionales Feld nicht angegeben" und "Feld wurde nicht absichtlich angegeben, um zu bedeuten, dass es null ist". Wenn Sie sich für diese Genauigkeit interessieren, können Sie dem Feld "google.protobuf.NullValue" ein zusätzliches Feld hinzufügen, mit dem Sie zwischen "Feld nicht angegeben", "Feld als Wert X angegeben" und "Feld als Null angegeben" unterscheiden können. . Es ist ein bisschen flüchtig, aber das liegt daran, dass proto3 null nicht direkt unterstützt wie JSON.
jschultz410
7

Um den Vorschlag von @cybersnoopy hier zu erweitern

Wenn Sie eine .proto-Datei mit einer Nachricht wie der folgenden hatten:

message Request {
    oneof option {
        int64 option_value = 1;
    }
}

Sie können die bereitgestellten Falloptionen verwenden (von Java generierter Code) :

So können wir jetzt folgenden Code schreiben:

Request.OptionCase optionCase = request.getOptionCase();
OptionCase optionNotSet = OPTION_NOT_SET;

if (optionNotSet.equals(optionCase)){
    // value not set
} else {
    // value set
}
Benjamin Slabbert
quelle
In Python ist es noch einfacher. Sie können einfach request.HasField ("option_value") ausführen. Wenn Ihre Nachricht eine Reihe solcher Singleton-Elemente enthält, können Sie wie bei einem normalen Skalar direkt auf die enthaltenen Skalare zugreifen.
jschultz410
-1

Eine andere Möglichkeit besteht darin, dass Sie für jedes optionale Feld eine Bitmaske verwenden können. und setzen Sie diese Bits, wenn Werte gesetzt sind, und setzen Sie die Bits zurück, deren Werte nicht gesetzt sind

enum bitsV {
    baz_present = 1; // 0x01
    baz1_present = 2; // 0x02

}
message Foo {
    uint32 bitMask;
    required int32 bar = 1;
    optional int32 baz = 2;
    optional int32 baz1 = 3;
}

Überprüfen Sie beim Parsen den Wert von bitMask.

if (bitMask & baz_present)
    baz is present

if (bitMask & baz1_present)
    baz1 is present
ChauhanTs
quelle
-2

Sie können feststellen, ob eine initialisiert wurde, indem Sie die Referenzen mit der Standardinstanz vergleichen:

GRPCContainer container = myGrpcResponseBean.getContainer();
if (container.getDefaultInstanceForType() != container) {
...
}
eduyayo
quelle
1
Dies ist kein guter allgemeiner Ansatz, da der Standardwert sehr oft ein vollkommen akzeptabler Wert für das Feld ist und Sie in dieser Situation nicht zwischen "Feld nicht vorhanden" und "Feld vorhanden, aber auf Standard gesetzt" unterscheiden können.
jschultz410