237 votes

Comment définir un champ optionnel dans protobuf 3

J'ai besoin de spécifier un message avec un champ optionnel dans protobuf (syntaxe proto3). En termes de syntaxe proto 2, le message que je veux exprimer est quelque chose comme :

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

D'après ce que j'ai compris, le concept "optionnel" a été supprimé de la syntaxe proto 3 (avec le concept obligatoire). Bien que l'alternative ne soit pas claire - utiliser la valeur par défaut pour indiquer qu'un champ n'a pas été spécifié par l'expéditeur, laisse une ambiguïté si la valeur par défaut appartient au domaine des valeurs valides (considérez par exemple un type booléen).

Alors, comment suis-je censé coder le message ci-dessus ? Merci.

0 votes

L'approche ci-dessous est-elle une bonne solution ? message NoBaz { } message Foo { int32 bar = 1 ; oneof baz { NoBaz undefined = 2 ; int32 defined = 3 ; } ; }.

2 votes

Il y a une version Proto 2 de cette question si d'autres personnes trouvent cela mais utilisent Proto 2.

3 votes

Proto3 rend tous les champs facultatifs. Cependant, pour les scalaires, ils ont rendu impossible la distinction entre "champ non défini" et "champ défini mais avec une valeur par défaut". Si vous enveloppez votre scalaire dans un singleton oneof, par exemple : - message blah { oneof v1 { int32 foo = 1 ; } }. }, vous pouvez alors vérifier à nouveau si foo a effectivement été défini ou non. Pour Python au moins, vous pouvez opérer directement sur foo comme s'il n'était pas à l'intérieur d'un oneof et vous pouvez demander HasField("foo").

189voto

Kenton Varda Points 2170

Dans proto3, tous les champs sont "facultatifs" (en ce sens que l'expéditeur ne commet pas d'erreur s'il ne les définit pas). Mais les champs ne sont plus "nullables", en ce sens qu'il n'y a aucun moyen de faire la différence entre un champ explicitement défini à sa valeur par défaut et un champ qui n'a pas été défini du tout.

Si vous avez besoin d'un état "nul" (et qu'il n'y a pas de valeur hors gamme que vous pouvez utiliser pour cela), vous devrez l'encoder dans un champ distinct. Par exemple, vous pourriez le faire :

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

Alternativement, vous pouvez utiliser oneof :

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

El oneof est plus explicite et plus efficace sur le fil mais nécessite de comprendre comment oneof le travail sur les valeurs.

Enfin, une autre option parfaitement raisonnable consiste à s'en tenir à proto2. Proto2 n'est pas déprécié, et en fait, de nombreux projets (y compris au sein de Google) dépendent beaucoup des fonctionnalités de proto2 qui sont supprimées dans proto3, donc ils ne changeront probablement jamais. Il est donc prudent de continuer à l'utiliser dans un avenir prévisible.

0 votes

Similaire à votre solution, dans mon commentaire, je proposais d'utiliser le oneof avec la valeur réelle et un type null (un message vide). De cette façon, on ne s'embête pas avec la valeur booléenne (qui ne devrait pas être pertinente, car s'il y a le booléen, alors il n'y a pas de baz_value) Correct ?

3 votes

@MaxP Votre solution fonctionne mais je recommanderais un booléen plutôt qu'un message vide. L'un ou l'autre prendra deux octets sur le fil, mais le message vide nécessitera beaucoup plus de CPU, de RAM et de code généré pour le gérer.

18 votes

Je découvre le message Foo { oneof baz { int32 baz_value = 1 ; } } fonctionne assez bien.

153voto

VM4 Points 1611

L'un des moyens est de optional comme décrit dans la réponse acceptée : https://stackoverflow.com/a/62566052/1803821

Une autre solution consiste à utiliser des objets enveloppants. Vous n'avez pas besoin de les écrire vous-même puisque Google les fournit déjà :

En haut de votre fichier .proto, ajoutez cette importation :

import "google/protobuf/wrappers.proto";

Vous pouvez maintenant utiliser des enveloppes spéciales pour chaque type simple :

DoubleValue
FloatValue
Int64Value
UInt64Value
Int32Value
UInt32Value
BoolValue
StringValue
BytesValue

Ainsi, pour répondre à la question initiale, l'utilisation d'un tel wrapper pourrait être la suivante :

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

Maintenant, par exemple, en Java, je peux faire des choses comme :

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

4 votes

Comment cela fonctionne-t-il ? Lorsque baz=null et quand baz n'est pas passé, dans les deux cas hasBaz() dit false !

1 votes

L'idée est simple : vous utilisez des objets enveloppants ou, en d'autres termes, des types définis par l'utilisateur. Ces objets enveloppants peuvent être manquants. L'exemple Java que j'ai fourni a bien fonctionné pour moi lorsque j'ai travaillé avec gRPC.

0 votes

Ouais ! Je comprends l'idée générale, mais je voulais le voir en action. Ce que je ne comprends pas, c'est que (même dans l'objet wrapper) " Comment identifier les valeurs manquantes et nulles des wrappers ? "

46voto

CyberSnoopy Points 144

Sur la base de la réponse de Kenton, une solution plus simple mais qui fonctionne ressemble à ceci :

message Foo {
    oneof optional_baz { // "optional_" prefix here just serves as an indicator, not keyword in proto2
        int32 baz = 1;
    }
}

0 votes

Comment cela incarne-t-il le caractère facultatif ?

28 votes

Fondamentalement, oneof est mal nommé. Il signifie "au plus un de". Il y a toujours une valeur nulle possible.

0 votes

Si elle n'est pas définie, la valeur du cas sera None (en C#) - voir l'enum-type pour la langue de votre choix.

8voto

Benjamin Slabbert Points 133

Pour développer la suggestion de @cybersnoopy aquí

si vous aviez un fichier .proto avec un message comme celui-ci :

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

Vous pouvez utiliser les options de l'affaire fourni (code généré en java) :

Nous pouvons donc maintenant écrire du code comme suit :

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

if (optionNotSet.equals(optionCase)){
    // value not set
} else {
    // value set
}

0 votes

En Python, c'est encore plus simple. Vous pouvez simplement faire request.HasField("option_value"). De plus, si vous avez un tas de singleton oneof comme celui-ci dans votre message, vous pouvez accéder directement aux scalaires qu'ils contiennent comme un scalaire normal.

-3voto

eduyayo Points 1743

Vous pouvez savoir si l'un d'eux a été initialisé en comparant les références avec l'instance par défaut :

GRPCContainer container = myGrpcResponseBean.getContainer();
if (container.getDefaultInstanceForType() != container) {
...
}

1 votes

Ce n'est pas une bonne approche générale car très souvent, la valeur par défaut est une valeur parfaitement acceptable pour le champ et dans cette situation, vous ne pouvez pas faire la distinction entre "champ absent" et "champ présent mais défini par défaut".

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X