关于协议缓冲区:如何在protobuf 3中定义可选字段

How to define an optional field in protobuf 3

我需要在protobuf(proto3语法)中指定一个带有可选字段的消息。 用proto 2语法来说,我要表达的信息是这样的:

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

据我了解,"可选"概念已从语法原型3中删除(以及必需的概念)。 尽管尚不清楚替代方案-使用默认值声明尚未从发送方指定字段,但如果默认值属于有效值域(例如,考虑布尔类型),则仍会造成歧义。

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


在proto3中,所有字段均为"可选"(如果发件人未能设置它们,这不是错误)。但是,字段不再是"可为空的",因为无法分辨出明确设置为默认值的字段与根本没有设置默认值之间的区别。

如果需要"空"状态(并且没有可用于此的超出范围的值),则需要将其编码为单独的字段。例如,您可以执行以下操作:

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

另外,您可以使用oneof

1
2
3
4
5
6
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中删除,因此它们可能永远不会切换。因此,在可预见的将来继续使用它是安全的。


一种方法是使用oneof,如接受的答案中建议的那样。

另一个是使用包装对象。您不需要自己编写它们,因为Google已经提供了它们:

在您的.proto文件顶部,添加以下导入:

import"google/protobuf/wrappers.proto";

现在,您可以对每种简单类型使用特殊包装器:

1
2
3
4
5
6
7
8
9
DoubleValue
FloatValue
Int64Value
UInt64Value
Int32Value
UInt32Value
BoolValue
StringValue
BytesValue

因此,要回答原始问题,此类包装器的用法可能是这样的:

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

现在以Java为例,我可以做类似的事情:

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


根据Kenton的回答,一个更简单但可行的解决方案如下所示:

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


从protobuf 3.12版开始,proto3支持使用optional关键字(与proto2中一样)来提供标量字段存在信息。

1
2
3
4
5
6
syntax ="proto3";

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

就像在proto2中一样,上面的optional字段会生成一个has_baz() / hasBaz()方法。

正如Cyber??Snoopy的答案所暗示的那样,protoc可以有效地将optional字段视为使用oneof包装器声明的字段。如果您已经使用了这种方法,则可以清理消息声明(从oneof切换到optional),因为线路格式相同。

您可以在应用说明:字段存在文档中的proto3中找到有关字段存在和optional的详细信息。

在版本3.12中,此功能需要将--experimental_allow_proto3_optional标志传递给protoc。该功能公告称它将"通常有望在3.13中可用"。


在这里扩展@cybersnoopy的建议

如果您有一个带有如下消息的.proto文件:

1
2
3
4
5
message Request {
    oneof option {
        int64 option_value = 1;
    }
}

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

因此,我们现在可以编写一些代码,如下所示:

1
2
3
4
5
6
7
8
Request.OptionCase optionCase = request.getOptionCase();
OptionCase optionNotSet = OPTION_NOT_SET;

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


关于此有一个很好的帖子:https://itnext.io/protobuf-and-null-support-1908a15311b6

解决方案取决于您的实际用例:

  • 处理部分更新

  • 支持空


另一种方法是可以对每个可选字段使用位掩码。并设置这些位(如果设置了值)并重置那些未设置值的位

1
2
3
4
5
6
7
8
9
10
11
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的值。

1
2
3
4
5
if (bitMask & baz_present)
    baz is present

if (bitMask & baz1_present)
    baz1 is present

您可以通过将引用与默认实例进行比较来查找是否已初始化:

1
2
3
4
GRPCContainer container = myGrpcResponseBean.getContainer();
if (container.getDefaultInstanceForType() != container) {
...
}