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

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

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

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

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

Ответ 1

В 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.

Наконец, еще один вполне разумный вариант - придерживаться proto2. Proto2 не устарел, и на самом деле многие проекты (в том числе внутри Google) очень сильно зависят от функций proto2, которые удалены в proto3, поэтому они, вероятно, никогда не переключатся. Таким образом, безопасно использовать его в обозримом будущем.

Ответ 2

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

На основе ответа Kenton более простое, но работающее решение выглядит следующим образом:

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

Ответ 4

Подробнее о предложении @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
}

Ответ 5

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

GRPCContainer container = myGrpcResponseBean.getContainer();
if (container.getDefaultInstanceForType() != container) {
...
}

Ответ 6

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

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