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

105

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

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

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

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

MaxP
источник
Подход, описанный ниже, является разумным решением? сообщение NoBaz {} сообщение Foo {int32 bar = 1; oneof baz {NoBaz undefined = 2; int32 определено = 3; }; }
MaxP 06
2
Есть версия этого вопроса для Proto 2 , если другие найдут ее, но используют Proto 2.
chwarr
1
proto3 в основном делает все поля необязательными. Однако для скаляров они сделали невозможным различие между «поле не установлено» и «поле установлено, но имеет значение по умолчанию». Если вы оберните свой скаляр в одноэлементный oneof, например - message blah {oneof v1 {int32 foo = 1; }}, затем вы можете еще раз проверить, действительно ли установлен foo или нет. По крайней мере, для Python вы можете работать непосредственно с foo, как если бы он не был внутри oneof, и вы можете спросить HasField ("foo").
jschultz410
1
@MaxP Может быть, вы могли бы изменить принятый ответ на stackoverflow.com/a/62566052/66465, так как теперь есть более новая версия protobuf 3optional
SebastianK

Ответы:

40

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

syntax = "proto3";

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

A has_baz()/ hasBaz()метод создается дляoptional поля выше, так же , как это было в proto2.

Под капотом protoc эффективно обрабатывает optionalполе, как если бы оно было объявлено с помощью oneofоболочки, как следует из ответа CyberSnoopy :

message Foo {
    int32 bar = 1;
    oneof optional_baz {
        int32 baz = 2;
    }
}

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

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

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

Обновление за октябрь 2020 г. Эта функция по-прежнему считается экспериментальной (требуется флаг) в версии 3.13 .

Jaredjacobs
источник
1
Вы случайно не знаете, как передать флаг на C #?
Джеймс Хэнкок,
Это лучший ответ сейчас, когда в proto3 добавлен улучшенный синтаксис. Отличный вызов, Джарад!
Эван Моран,
Просто чтобы добавить optional int xyz: 1) has_xyzопределяет, было ли установлено дополнительное значение; 2) clear_xyzсбрасывает значение. Подробнее здесь: github.com/protocolbuffers/protobuf/blob/master/docs/…
Эван Моран,
@JamesHancock или Java?
Тоби Акиниеми,
@TobiAkinyemi ??
Джеймс Хэнкок,
123

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

Кентон Варда
источник
Как и ваше решение, в моем комментарии я предложил использовать oneof с реальным значением и нулевым типом (пустое сообщение). Таким образом, вы не беспокоитесь о логическом значении (которое не должно иметь значения, потому что, если есть логическое значение, то нет и baz_value) Правильно?
MaxP 07
2
@MaxP Ваше решение работает, но я бы рекомендовал логическое значение вместо пустого сообщения. Любой из них займет два байта в сети, но для обработки пустого сообщения потребуется значительно больше ЦП, ОЗУ и сгенерированного кода.
Кентон Варда
13
Я обнаружил сообщение Foo {oneof baz {int32 baz_value = 1; }} работает очень хорошо.
CyberSnoopy
@CyberSnoopy Можете опубликовать это как ответ? Ваше решение работает идеально и элегантно.
Ченг Чен
@CyberSnoopy Вы случайно не сталкивались с какими-либо проблемами при отправке ответного сообщения, которое имеет примерно такую ​​структуру: message FooList {Repeated Foo foos = 1; }? Ваше решение отличное, но сейчас у меня проблемы с отправкой FooList в качестве ответа сервера.
Caffeinate Часто
95

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

VM4
источник
3
Как это работает? Когда baz=nullи когда bazне проходит, hasBaz()говорят оба случая false!
mayankcpdixit 03
Идея проста: вы используете объекты-оболочки или, другими словами, определяемые пользователем типы. Эти объекты-оболочки могут отсутствовать. Приведенный мной пример Java хорошо работал у меня при работе с gRPC.
VM4
Да уж! Я понимаю общую идею, но я хотел увидеть ее в действии. Я не понимаю: (даже в объекте-
оболочке
2
Это путь. В C # сгенерированный код создает свойства Nullable <T>.
Аарон Худон
5
Лучше оригинального авснера!
Dev Aggarwal
32

Основываясь на ответе Кентона, более простое, но рабочее решение выглядит так:

message Foo {
    oneof optional_baz { // "optional_" prefix here just serves as an indicator, not keyword in proto2
        int32 baz = 1;
    }
}
CyberSnoopy
источник
как это воплощает необязательный символ?
JFFIGK
19
В основном oneof плохо назван. Это означает «максимум один из». Всегда есть возможное нулевое значение.
ecl3ctic
Если не задано, регистр значения будет None(в C #) - см. Enum-type для выбранного вами языка.
nitzel
Да, это, вероятно, лучший способ снять шкуру с этой кошки в proto3 - даже если он делает .proto немного некрасивым.
jschultz410
Однако это в некоторой степени подразумевает, что вы можете интерпретировать отсутствие поля как явную установку для него нулевого значения. Другими словами, существует некоторая двусмысленность между «необязательное поле не указано» и «поле не было специально указано, чтобы означать, что оно пустое». Если вам важен такой уровень точности, вы можете добавить дополнительное поле google.protobuf.NullValue к oneof, которое позволяет вам различать «поле не указано», «поле указано как значение X» и «поле указано как нулевое». . Это как-то странно, но это потому, что proto3 не поддерживает null напрямую, как JSON.
jschultz410
7

Чтобы расширить предложение @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
}
Бенджамин Слабберт
источник
В Python все еще проще. Вы можете просто сделать request.HasField ("option_value"). Кроме того, если у вас есть куча одноэлементных oneof, подобных этому, внутри вашего сообщения, вы можете получить доступ к содержащимся в них скалярам напрямую, как обычный скаляр.
jschultz410
-1

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

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
ЧауханЦ
источник
-2

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

GRPCContainer container = myGrpcResponseBean.getContainer();
if (container.getDefaultInstanceForType() != container) {
...
}
Eduyayo
источник
1
Это не очень хороший общий подход, потому что очень часто значение по умолчанию является вполне приемлемым значением для поля, и в этой ситуации вы не можете различить «поле отсутствует» и «поле присутствует, но установлено по умолчанию».
jschultz410