Как определить необязательное поле в 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) {
...
}