如何在 protobuf 3 中定义可选字段
How to define an optional field in protobuf 3
我需要在 protobuf(proto3 语法)中指定一个带有可选字段的消息。在 proto 2 语法方面,我想表达的信息是这样的:
message Foo {
required int32 bar = 1;
optional int32 baz = 2;
}
根据我的理解,"optional" 概念已从语法原型 3 中删除(连同必需的概念)。尽管替代方案尚不清楚——使用默认值来声明发件人未指定字段,但如果默认值属于有效值域(例如考虑布尔类型),则会产生歧义。
那么,我应该如何对上面的消息进行编码?谢谢。
在 proto3 中,所有字段都是 "optional"(因为如果发送方未能设置它们,这不是错误)。但是,字段不再是 "nullable",因为无法区分字段被显式设置为其默认值与根本未设置之间的区别。
如果您需要 "null" 状态(并且没有可用于此的超出范围的值),那么您需要将其编码为一个单独的字段。例如,您可以这样做:
message Foo {
bool has_baz = 1; // always set this to "true" when using baz
int32 baz = 2;
}
或者,您可以使用 oneof
:
message Foo {
oneof baz {
bool baz_null = 1; // always set this to "true" when null
int32 baz_value = 2;
}
}
oneof
版本更明确、更有效,但需要了解 oneof
值的工作原理。
最后,另一个完全合理的选择是坚持使用 proto2。 Proto2 并没有被弃用,事实上许多项目(包括 Google 内部)非常依赖 proto2 功能,这些功能在 proto3 中被删除,因此它们可能永远不会切换。因此,在可预见的未来继续使用它是安全的。
根据 Kenton 的回答,一个更简单但有效的解决方案如下所示:
message Foo {
oneof optional_baz { // "optional_" prefix here just serves as an indicator, not keyword in proto2
int32 baz = 1;
}
}
一种方法是 optional
就像接受的答案中描述的那样:
另一种是使用包装器对象。您不需要自己编写它们,因为 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()) { ... }
您可以通过将引用与默认实例进行比较来查看是否已初始化:
GRPCContainer container = myGrpcResponseBean.getContainer();
if (container.getDefaultInstanceForType() != container) {
...
}
扩展@cybersnoopy 的建议
如果您的 .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
}
另一种方法是您可以为每个可选字段使用位掩码。如果设置了值,则设置这些位,并重置那些未设置值的位
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
由于 protobuf release 3.15,proto3 支持使用 optional
关键字(就像在 proto2 中一样)来提供标量字段存在信息。
syntax = "proto3";
message Foo {
int32 bar = 1;
optional int32 baz = 2;
}
为上面的optional
字段生成了一个has_baz()
/hasBaz()
方法,就像在proto2中一样。
在幕后,protoc 有效地将 optional
字段视为使用 oneof
包装器声明,正如 建议的那样:
message Foo {
int32 bar = 1;
oneof optional_baz {
int32 baz = 2;
}
}
如果您已经使用过这种方法,您现在可以简化您的消息声明(从 oneof
切换到 optional
)和代码,因为有线格式是相同的。
有关字段存在和 proto3 中 optional
的基本细节可以在 Application note: Field presence 文档中找到。
历史记录:proto3 中对 optional
的实验性支持于 2020 年 4 月 23 日在 this comment 中首次宣布。使用它需要在版本 3.12-3.14 中传递 protoc --experimental_allow_proto3_optional
标志。
另一种对您想要的消息进行编码的方法是添加另一个字段来跟踪“设置”字段:
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)
只需使用:
syntax = "proto3";
message Hello {
int64 required_id = 1;
optional int64 optional_id = 2;
}
在 Go 中,它使用
构建结构
type Hello struct {
...
RequiredId int64 ...
OptionalId *int64 ...
...
}
您可以轻松检查 nil
并区分默认值(零)和未设置值(nil)。
这里的大多数答案都已过时且不必要地复杂。
我需要在 protobuf(proto3 语法)中指定一个带有可选字段的消息。在 proto 2 语法方面,我想表达的信息是这样的:
message Foo {
required int32 bar = 1;
optional int32 baz = 2;
}
根据我的理解,"optional" 概念已从语法原型 3 中删除(连同必需的概念)。尽管替代方案尚不清楚——使用默认值来声明发件人未指定字段,但如果默认值属于有效值域(例如考虑布尔类型),则会产生歧义。
那么,我应该如何对上面的消息进行编码?谢谢。
在 proto3 中,所有字段都是 "optional"(因为如果发送方未能设置它们,这不是错误)。但是,字段不再是 "nullable",因为无法区分字段被显式设置为其默认值与根本未设置之间的区别。
如果您需要 "null" 状态(并且没有可用于此的超出范围的值),那么您需要将其编码为一个单独的字段。例如,您可以这样做:
message Foo {
bool has_baz = 1; // always set this to "true" when using baz
int32 baz = 2;
}
或者,您可以使用 oneof
:
message Foo {
oneof baz {
bool baz_null = 1; // always set this to "true" when null
int32 baz_value = 2;
}
}
oneof
版本更明确、更有效,但需要了解 oneof
值的工作原理。
最后,另一个完全合理的选择是坚持使用 proto2。 Proto2 并没有被弃用,事实上许多项目(包括 Google 内部)非常依赖 proto2 功能,这些功能在 proto3 中被删除,因此它们可能永远不会切换。因此,在可预见的未来继续使用它是安全的。
根据 Kenton 的回答,一个更简单但有效的解决方案如下所示:
message Foo {
oneof optional_baz { // "optional_" prefix here just serves as an indicator, not keyword in proto2
int32 baz = 1;
}
}
一种方法是 optional
就像接受的答案中描述的那样:
另一种是使用包装器对象。您不需要自己编写它们,因为 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()) { ... }
您可以通过将引用与默认实例进行比较来查看是否已初始化:
GRPCContainer container = myGrpcResponseBean.getContainer();
if (container.getDefaultInstanceForType() != container) {
...
}
扩展@cybersnoopy 的建议
如果您的 .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
}
另一种方法是您可以为每个可选字段使用位掩码。如果设置了值,则设置这些位,并重置那些未设置值的位
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
由于 protobuf release 3.15,proto3 支持使用 optional
关键字(就像在 proto2 中一样)来提供标量字段存在信息。
syntax = "proto3";
message Foo {
int32 bar = 1;
optional int32 baz = 2;
}
为上面的optional
字段生成了一个has_baz()
/hasBaz()
方法,就像在proto2中一样。
在幕后,protoc 有效地将 optional
字段视为使用 oneof
包装器声明,正如
message Foo {
int32 bar = 1;
oneof optional_baz {
int32 baz = 2;
}
}
如果您已经使用过这种方法,您现在可以简化您的消息声明(从 oneof
切换到 optional
)和代码,因为有线格式是相同的。
有关字段存在和 proto3 中 optional
的基本细节可以在 Application note: Field presence 文档中找到。
历史记录:proto3 中对 optional
的实验性支持于 2020 年 4 月 23 日在 this comment 中首次宣布。使用它需要在版本 3.12-3.14 中传递 protoc --experimental_allow_proto3_optional
标志。
另一种对您想要的消息进行编码的方法是添加另一个字段来跟踪“设置”字段:
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)
只需使用:
syntax = "proto3";
message Hello {
int64 required_id = 1;
optional int64 optional_id = 2;
}
在 Go 中,它使用
构建结构type Hello struct {
...
RequiredId int64 ...
OptionalId *int64 ...
...
}
您可以轻松检查 nil
并区分默认值(零)和未设置值(nil)。
这里的大多数答案都已过时且不必要地复杂。