如何在protobuf 3中定义可选字段

时间:2017-03-06 09:34:09

标签: protocol-buffers protocol-buffers-3

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

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

根据我的理解"可选"已从语法proto 3中删除了概念(以及所需的概念)。虽然不清楚替代方法 - 使用默认值来声明尚未从发送方指定字段,但如果默认值属于有效值域,则会留下歧义(例如考虑布尔类型)。

那么,我该怎么编码上面的消息呢?谢谢。

10 个答案:

答案 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中查找特定语言的“单个消息字段”。

例如-Singular message fields in Python