我需要在protobuf(proto3语法)中指定带有可选字段的消息。就proto 2语法而言,我想表达的信息是:
message Foo {
required int32 bar = 1;
optional int32 baz = 2;
}
根据我的理解"可选"已从语法proto 3中删除了概念(以及所需的概念)。虽然不清楚替代方法 - 使用默认值来声明尚未从发送方指定字段,但如果默认值属于有效值域,则会留下歧义(例如考虑布尔类型)。
那么,我该怎么编码上面的消息呢?谢谢。
答案 0 :(得分:65)
在proto3中,所有字段都是“可选的”(因为如果发件人未能设置它们,则不是错误)。但是,字段不再是“可空的”,因为无法区分显式设置为默认值的字段与未设置的字段之间的区别。
如果您需要“null”状态(并且没有可用于此的超出范围的值),那么您将需要将其编码为单独的字段。例如,您可以这样做:
message Foo {
bool has_baz = 1;
int32 baz = 2;
}
或者,您可以使用oneof
:
message Foo {
oneof baz {
bool baz_null = 1; // always set this to "true" when using
int32 baz_value = 2;
}
}
oneof
版本在网络上更明确,更高效,但需要了解oneof
值的工作原理。
最后,另一个完全合理的选择是坚持使用proto2。 Proto2并没有被弃用,事实上很多项目(包括Google内部)都非常依赖proto2中删除的proto2功能,因此它们可能永远不会切换。因此,在可预见的将来继续使用它是安全的。
答案 1 :(得分:51)
自protobuf release 3.12起,proto3在实验上支持使用optional
关键字(与proto2中一样)来提供标量字段存在信息。
syntax = "proto3";
message Foo {
int32 bar = 1;
optional int32 baz = 2;
}
上面的has_baz()
字段会生成一个hasBaz()
/ optional
方法,就像在proto2中一样。
在幕后,protoc有效地对待optional
字段,就像使用oneof
包装器声明了它一样,CyberSnoopy’s answer建议:
message Foo {
int32 bar = 1;
oneof optional_baz {
int32 baz = 2;
}
}
如果您已经使用了该方法,则proto3 oneof
支持实验状态的毕业生后,您就可以清理您的消息声明(从optional
切换到optional
) ,因为接线格式相同。
您可以在Application note: Field presence文档的proto3中找到有关字段存在和optional
的详细信息。
将--experimental_allow_proto3_optional
标志传递给协议以使用版本3.12中的此功能。 feature announcement说它将“通常有望在3.13中可用”。
2020年11月更新:该功能在release 3.14中仍被认为是实验性的(需要标记)。 signs正在取得进展。
答案 2 :(得分:39)
一种方法是在接受的答案中使用oneof
之类的建议。
另一个是使用包装器对象。你不需要像google已经提供的那样自己编写它们:
在.proto文件的顶部添加此导入:
import "google/protobuf/wrappers.proto";
现在,您可以为每种简单类型使用特殊包装:
DoubleValue
FloatValue
Int64Value
UInt64Value
Int32Value
UInt32Value
BoolValue
StringValue
BytesValue
因此,为了回答原始问题,使用这样的包装器可能是这样的:
message Foo {
int32 bar = 1;
google.protobuf.Int32Value baz = 2;
}
现在,例如在Java中我可以做类似的事情:
if(foo.hasBaz()) { ... }
答案 3 :(得分:14)
根据Kenton的回答,一个更简单但更有效的解决方案如下:
message Foo {
oneof optional_baz { // "optional_" prefix here just serves as an indicator, not keyword in proto2
int32 baz = 1;
}
}
答案 4 :(得分:2)
详细介绍@cybersnoopy的建议here
如果您有一个.proto文件,并显示如下消息:
message Request {
oneof option {
int64 option_value = 1;
}
}
您可以使用案例选项provided (java generated code):
所以我们现在可以编写一些代码,如下所示:
Request.OptionCase optionCase = request.getOptionCase();
OptionCase optionNotSet = OPTION_NOT_SET;
if (optionNotSet.equals(optionCase)){
// value not set
} else {
// value set
}
答案 5 :(得分:1)
关于此的好帖子:https://itnext.io/protobuf-and-null-support-1908a15311b6
解决方案取决于您的实际用例:
答案 6 :(得分:1)
编码您想要的消息的另一种方法是添加另一个字段来跟踪“设置”字段:
syntax="proto3";
package qtprotobuf.examples;
message SparseMessage {
repeated uint32 fieldsUsed = 1;
bool attendedParty = 2;
uint32 numberOfKids = 3;
string nickName = 4;
}
message ExplicitMessage {
enum PARTY_STATUS {ATTENDED=0; DIDNT_ATTEND=1; DIDNT_ASK=2;};
PARTY_STATUS attendedParty = 1;
bool indicatedKids = 2;
uint32 numberOfKids = 3;
enum NO_NICK_STATUS {HAS_NO_NICKNAME=0; WOULD_NOT_ADMIT_TO_HAVING_HAD_NICKNAME=1;};
NO_NICK_STATUS noNickStatus = 4;
string nickName = 5;
}
如果存在大量字段并且仅分配了少量字段,则这特别合适。
在python中,用法如下:
import field_enum_example_pb2
m = field_enum_example_pb2.SparseMessage()
m.attendedParty = True
m.fieldsUsed.append(field_enum_example_pb2.SparseMessages.ATTENDEDPARTY_FIELD_NUMBER)
答案 7 :(得分:0)
您可以通过将引用与默认实例进行比较来找到是否已初始化了一个:
GRPCContainer container = myGrpcResponseBean.getContainer();
if (container.getDefaultInstanceForType() != container) {
...
}
答案 8 :(得分:0)
另一种方法是,可以对每个可选字段使用位掩码。并设置这些位(如果设置了值),然后重置那些未设置值的位
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;
}
在解析时检查bitMask的值。
if (bitMask & baz_present)
baz is present
if (bitMask & baz1_present)
baz1 is present
答案 9 :(得分:0)
@ VM4的答案是实现此目的的 方法。
在这里,您可以轻松创建自定义包装类型,而无需担心oneOf
语法。
message Challenge {
string question = 1;
Reply reply = 2; // reference to a wrapper type
}
// wrapper type for a string
message Reply {
string body = 1;
}
然后在应用程序代码中,(Python)
challenge = Challenge()
challenge.HasField('reply') # False
challenge = Challenge(reply=Reply())
challenge.HasField('reply') # True
某些语言使用<type>.has<field>()
语法,因此您必须在API reference中查找特定语言的“单个消息字段”。