如何在 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)。

这里的大多数答案都已过时且不必要地复杂。