如何定义一个可选的领域在原型3

我需要在 Protobuf (Proto3语法)中指定一条带有可选字段的消息。就 proto 2语法而言,我想表达的信息是这样的:

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

根据我的理解,“可选”的概念已经从语法原型3中移除(连同必需的概念)。尽管不清楚替代方案——使用默认值来声明未从发送方指定字段,但如果默认值属于有效值域(例如,考虑布尔类型) ,则会造成模糊性。

那么,我该如何编码上面的信息呢? 谢谢。

202854 次浏览

在 Proto3中,所有字段都是“可选的”(因为如果发送方未能设置它们,那么它就不是一个错误)。但是,字段不再是“可空的”,因为没有办法区分显式设置为默认值的字段和根本没有设置的字段。

如果您需要一个“ 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值是如何工作的。

最后,另一个完全合理的选择是坚持使用原型2。Proto2并没有被废弃,事实上许多项目(包括 Google 内部)非常依赖于 Proto2的特性,而这些特性在 Proto3中已经被删除了,因此它们可能永远也不会改变。因此,在可预见的将来继续使用它是安全的。

基于肯顿的回答,一个更简单但可行的解决方案看起来像:

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

一种方法是像公认的答案中描述的 optional: https://stackoverflow.com/a/62566052/1803821

另一种方法是使用包装对象,你不需要自己编写它们,因为谷歌已经提供了这些对象:

在.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;
}
}

你可以使用案例选项 提供(Java 生成的代码):

现在我们可以编写如下代码:

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 3.15版本开始,Proto3就支持使用 optional关键字(就像在 Proto2中一样)来提供标量字段存在信息。

syntax = "proto3";


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

为上面的 optional字段生成了一个 has_baz()/hasBaz()方法,就像它在原型2中一样。

在引擎盖下,proc 有效地对待 optional字段,就像它是使用 oneof包装器声明的一样,正如 赛博史努比的回答所建议的:

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

如果您已经使用过这种方法,现在可以简化消息声明(从 oneof切换到 optional)和代码,因为有线格式是相同的。

关于现场存在和原型3中的 optional的细节可以在 申请说明: 实地考察文档中找到。

历史记录: 原型3中对 optional的实验支持最早于2020年4月23日在 此评论中宣布。使用它需要在3.12-3.14版本中传递协议 --experimental_allow_proto3_optional标志。

对消息进行编码的另一种方法是添加另一个字段来跟踪“ set”字段:

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并区分默认值(零)和未设置值(零)。

这里的大多数答案都是过时的和不必要的复杂的。