Обновление protobuf от версии 2 до 3 - несовместимо с значениями по умолчанию protobuf
Я пытаюсь перейти на версию protobuf версии 3 и оставаться в обратной совместимости с версией 2. Кажется, что работает, кроме одного - в прото-2 вы можете установить свои собственные значения по умолчанию, но в прото 3 вы не можете. Если вы выбрали значение по умолчанию в прото-2, которое не является стандартным значением по умолчанию для прото-3, у вас возникнет проблема. Например, в прото-2:
message Record {
required uint32 fileno = 1;
required uint64 pos = 2;
optional uint64 bmsPos = 3 [default = 0];
optional uint32 scanMode = 4 [default = 9999];
}
теперь в прото-3 должно быть:
message Record {
uint32 fileno = 1;
uint64 pos = 2;
uint64 bmsPos = 3;
uint32 scanMode = 4;
}
Как в прото-2, так и в proto-3 в сообщении отправляются отсутствующие значения arent. Но proto-3 API не сообщает вам, есть ли значение по умолчанию в сообщении или нет, оно просто сообщает вам значение.
Итак, приемник proto-3 получает сообщение и сообщает мне, что scanMode = 0. Если это сообщение поступило от отправителя прото-2, то либо 1) отправитель прото-2 разместил 0 в сообщении, либо 2) отправитель proto-2 устанавливает значение 9999 (значение по умолчанию), поэтому значение не отправляется, и приемник proto-3 интерпретирует его как 0. Не зная, присутствует ли значение в сообщении или нет, код не может быть однозначным, даже если он знает, было ли сообщение отправлено от отправителя прото-2 или прото-3.
Обратите внимание, что в примере нет проблемы с полем bmsPos, поскольку сообщение proto-2 использует то же значение по умолчанию, что и proto-3 (0). Но если вы случайно выбрали значение по умолчанию не такое же, как у proto-3, то я не вижу, как обновиться до прото-3 и быть обратно совместимым.
Ответы
Ответ 1
Оказывается, есть способ узнать, действительно ли значение по умолчанию отсутствует или нет (спасибо некоторым друзьям в Google за этот ответ):
message Record {
uint32 fileno = 1;
uint64 pos = 2;
uint64 bmsPos = 3;
oneof scanMode_present {
uint32 scanMode = 4;
}
uint32 version = 5; // set to >= 3 for protobuf 3
}
Генерирующий код имеет дополнительные методы для определения того, установлено ли одно из полей, используя метод getXXXcase():
int scanMode = proto.getScanMode();
boolean isMissing = proto.getScanModePresentCase() == Record.ScanModePresentCase.SCANMODEPRESENT_NOT_SET;
if (isMissing) {
boolean isProto3 = proto.getVersion() >= 3;
scanMode = (isProto3) ? 0 : 9999;
}
- Обратите внимание, что имя oneof является произвольным, я принял поле fieldname_present.
- Oneof не добавляет ничего в формат проводов, поэтому он остается совместимым с сообщениями proto-2.
- Вы можете добавить информацию о версии в любом месте, что имеет смысл, я поместил ее в сообщение "Запись" для этого примера.
С помощью этого "трюка" я обновил до proto-3 с обратной совместимостью с нестандартными значениями прото-2 по умолчанию.