Как определить необязательное поле в protobuf 3

Мне нужно указать сообщение с необязательным полем в protobuf (синтаксис proto3). С точки зрения синтаксиса proto 2, сообщение, которое я хочу выразить, выглядит примерно так:

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

Из моего понимания "необязательная" концепция была удалена из синтаксиса proto 3 (вместе с необходимой концепцией). Хотя альтернатива не ясна - использование значения по умолчанию для указания того, что поле не было задано отправителем, оставляет двусмысленность, если значение по умолчанию принадлежит домену допустимых значений (рассмотрим, например, логический тип).

Итак, как я должен закодировать сообщение выше? Спасибо.

10 ответов

Решение

В proto3 все поля являются "необязательными" (в том случае, если отправитель не может их задать). Но поля больше не являются "обнуляемыми", так как нет способа определить разницу между полем, для которого явно задано значение по умолчанию, и тем, которое не было установлено вообще.

Если вам нужно состояние "ноль" (и нет значения вне диапазона, которое вы можете использовать для этого), вам вместо этого нужно будет закодировать это как отдельное поле. Например, вы можете сделать:

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, которые удалены в proto3, поэтому они, вероятно, никогда не переключатся. Таким образом, безопасно использовать его в обозримом будущем.

Начиная с версии 3.12 protobuf, proto3 поддерживает использованиеoptional ключевое слово (как в proto2), чтобы предоставить информацию о наличии скалярного поля.

syntax = "proto3";

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

А has_baz()/hasBaz() генерируется для optional поле выше, как и в proto2.

Под капотом protoc эффективно лечит optional поле, как если бы оно было объявлено с помощью oneofобертка, как предлагает ответ CyberSnoopy. Если вы уже использовали этот подход, вы можете очистить объявления сообщений (переключитесь сoneof к optional), поскольку формат провода такой же.

Вы можете найти мельчайшие подробности о присутствии на местах и optionalв proto3 в примечании к приложению: Док.

В версии 3.12 эта функция требует передачи --experimental_allow_proto3_optionalфлаг в протокол. В объявлении говорится, что она будет "общедоступной, надеюсь, в версии 3.13".

Одним из способов является использование 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()) { ... }

Исходя из ответа Кентона, более простое, но работающее решение выглядит так:

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

Чтобы расширить предложение @cybersnoopy здесь

если у вас был файл.proto с таким сообщением:

message Request {
    oneof option {
        int64 option_value = 1;
    }
}

Вы можете использовать предоставленные опции case (сгенерированный Java-код):

Теперь мы можем написать некоторый код следующим образом:

Request.OptionCase optionCase = request.getOptionCase();
OptionCase optionNotSet = OPTION_NOT_SET;

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

Просто используйте:

      syntax = "proto3";

message Hello {
    int64 required_id = 1;
    optional int64 optional_id = 2;
}

В Go он строит структуру с

      type Hello struct {
   ...
   RequiredId int64 ...
   OptionalId *int64 ...
   ...
}

Вы легко можете проверить nilи различать значение по умолчанию (ноль) и неустановленное значение (ноль).

Большинство ответов здесь устарели и излишне сложны.

Еще один способ закодировать сообщение, которое вы намереваетесь - добавить еще одно поле для отслеживания "установленных" полей:

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)

Об этом есть хороший пост: https://itnext.io/protobuf-and-null-support-1908a15311b6

Решение зависит от вашего фактического использования:

Другой способ - использовать битовую маску для каждого необязательного поля. и установить эти биты, если значения установлены, и сбросить те биты, значения которых не установлены

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

Вы можете узнать, был ли он инициализирован, сравнив ссылки с экземпляром по умолчанию:

GRPCContainer container = myGrpcResponseBean.getContainer();
if (container.getDefaultInstanceForType() != container) {
...
}
Другие вопросы по тегам