ProtoJSON

Охватывает, как использовать утилиты преобразования Protobuf в JSON.

Protobuf поддерживает каноническое кодирование в JSON, что упрощает обмен данными с системами, которые не поддерживают стандартный двоичный формат передачи protobuf.

На этой странице указан формат, но ряд дополнительных крайних случаев, которые определяют соответствующий парсер ProtoJSON, охвачены в Наборе тестов на соответствие Protobuf и не подробно описаны здесь.

Нецели формата

Не может представлять некоторые схемы JSON

Формат ProtoJSON предназначен для представления JSON схем, которые выразимы на языке схем Protobuf.

Может быть возможно представить многие предсуществующие схемы JSON как схему Protobuf и разобрать ее с помощью ProtoJSON, но он не предназначен для возможности представления произвольных схем JSON.

Например, нет способа выразить в схеме Protobuf типы, которые могут быть распространены в схемах JSON, такие как number[][] или number|string.

Возможно использовать типы google.protobuf.Struct и google.protobuf.Value, чтобы разрешить произвольный JSON быть разобранным в схему Protobuf, но они только позволяют вам захватывать значения как неупорядоченные карты ключ-значение без схемы.

Не так эффективен, как двоичный формат передачи

Формат ProtoJSON не так эффективен, как двоичный формат передачи, и никогда не будет.

Конвертер использует больше CPU для кодирования и декодирования сообщений и (за исключением редких случаев) закодированные сообщения потребляют больше места.

Не имеет таких хороших гарантий эволюции схемы, как двоичный формат передачи

Формат ProtoJSON не поддерживает неизвестные поля и помещает имена полей и значений перечислений в закодированные сообщения, что делает гораздо более сложным изменение этих имен позже. Удаление полей является критическим изменением, которое вызовет ошибку разбора.

См. Безопасность JSON Wire ниже для более подробной информации.

Описание формата

Представление каждого типа

Следующая таблица показывает, как данные представлены в файлах JSON.

Protobuf JSON Пример JSON Примечания
message object {"fooBar": v, "g": null, ...} Генерирует объекты JSON. Имена полей сообщения преобразуются в lowerCamelCase и становятся ключами объектов JSON. Если указана опция поля json_name, указанное значение будет использоваться как ключ вместо этого. Парсеры принимают как имя lowerCamelCase (или указанное опцией json_name), так и исходное имя поля proto. null является допустимым значением для всех типов полей и оставляет поле неустановленным. \0 (nul) не может использоваться внутри значения json_name. Подробнее о причинах см. Более строгая проверка для json_name.
enum string "FOO_BAR" Используется имя значения перечисления, указанное в proto. Парсеры принимают как имена перечислений, так и целочисленные значения.
map<K,V> object {"k": v, ...} Все ключи преобразуются в строки (ключи в спецификации JSON могут быть только строками).
repeated V array [v, ...] null принимается как пустой список [].
bool true, false true, false
string string "Hello World!"
bytes base64 string "YWJjMTIzIT8kKiYoKSctPUB+" Значением JSON будут данные, закодированные как строка с использованием стандартного base64 кодирования с заполнением. Принимаются как стандартное, так и URL-безопасное base64 кодирование с/без заполнения.
int32, fixed32, uint32 number 1, -10, 0 Значением JSON будет десятичное число. Принимаются как числа, так и строки. Пустые строки недопустимы. Экспоненциальная нотация (например, `1e2`) принимается как в кавычках, так и без них.
int64, fixed64, uint64 string "1", "-10" Значением JSON будет десятичная строка. Принимаются как числа, так и строки. Пустые строки недопустимы. Экспоненциальная нотация (например, `1e2`) принимается как в кавычках, так и без них.
float, double number 1.1, -10.0, 0, "NaN", "Infinity" Значением JSON будет число или одно из специальных строковых значений "NaN", "Infinity" и "-Infinity". Принимаются как числа, так и строки. Пустые строки недопустимы. Экспоненциальная нотация также принимается.
Any object {"@type": "url", "f": v, ... } Если Any содержит известный тип, который имеет специальное отображение JSON в этой таблице (например, google.protobuf.Duration), он будет преобразован следующим образом: {"@type": xxx, "value": yyy}. В противном случае значение будет преобразовано в объект JSON как обычно, и будет вставлено дополнительное поле "@type" со значением URL, указывающим тип сообщения.
Timestamp string "1972-01-01T10:00:20.021Z" Использует RFC 3339 (см. уточнение), где сгенерированный вывод всегда будет Z-нормализован и использует 0, 3, 6 или 9 дробных цифр. Смещения, отличные от "Z", также принимаются.
Duration string "1.000340012s", "1s" Сгенерированный вывод всегда содержит 0, 3, 6 или 9 дробных цифр, в зависимости от требуемой точности, с последующим суффиксом "s". Принимаются любые дробные цифры (также отсутствующие), если они помещаются в точность наносекунд и требуется суффикс "s". Это *не* формат 'duration' RFC 3339 (см. Durations для уточнения).
Struct object { ... } Любой объект JSON. См. struct.proto.
Wrapper types various types 2, "2", "foo", true, "true", null, 0, ... Обертки используют то же представление в JSON, что и обернутый примитивный тип, за исключением того, что null разрешен и сохраняется во время преобразования и передачи данных.
FieldMask string "f.fooBar,h" См. field_mask.proto.
ListValue array [foo, bar, ...]
Value value Любое значение JSON. Проверьте google.protobuf.Value для деталей.
NullValue null JSON null. Особый случай [поведения разбора null](#null-values).
Empty object {} Пустой объект JSON

Присутствие и значения по умолчанию

При генерации вывода в формате JSON из protocol buffer, если поле поддерживает присутствие, сериализаторы должны выдавать значение поля тогда и только тогда, когда соответствующий hasser вернул бы true.

Если поле не поддерживает присутствие поля и имеет значение по умолчанию (например, любое пустое повторяющееся поле), сериализаторы должны опускать его из вывода. Реализация может предоставлять опции для включения полей со значениями по умолчанию в вывод.

Null значения

Сериализаторы не должны выдавать значения null.

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

  • Любая проверка допустимости ключа все равно должна происходить (запрет неизвестных полей)
  • Поле должно оставаться неустановленным, как если бы его не было во входных данных вообще (hassers все равно должны возвращать false, где применимо).

Значения null не допускаются внутри повторяющихся полей.

google.protobuf.NullValue является особым исключением из этого поведения: null обрабатывается как сторожевой значение присутствия для этого типа, и поэтому поле этого типа должно обрабатываться сериализаторами и парсерами в соответствии со стандартным поведением присутствия. Это поведение соответственно позволяет google.protobuf.Struct и google.protobuf.Value без потерь кругло обрабатывать произвольный JSON.

Дублирующиеся значения

Сериализаторы никогда не должны сериализовать одно и то же поле несколько раз, ни несколько разных случаев в одном oneof в одном объекте JSON.

Парсеры должны принимать дублирование одного и того же поля, и должно сохраняться последнее предоставленное значение. Это также применяется к "альтернативным написаниям" одного и того же имени поля.

Если реализации не могут поддерживать необходимую информацию о порядке полей, предпочтительнее отклонять входные данные с дублирующимися ключами, чем иметь произвольное значение победителем. В некоторых реализациях поддержание порядка полей объектов может быть непрактичным или неосуществимым, поэтому настоятельно рекомендуется, чтобы системы избегали reliance на конкретное поведение для дублирующихся полей в ProtoJSON, где это возможно.

Числовые значения вне диапазона

При разборе числового значения, если число, которое разбирается из провода, не помещается в соответствующий тип, парсер должен принудительно преобразовать значение к соответствующему типу. Это имеет то же поведение, что и простое приведение в C++ или Java (например, если число больше 2^32 читается для поля int32, оно будет усечено до 32 бит).

Безопасность ProtoJSON Wire

При использовании ProtoJSON только некоторые изменения схемы безопасно делать в распределенной системе. Это контрастирует с теми же концепциями, примененными к двоичному формату передачи.

Небезопасные для JSON Wire изменения

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

  • Изменение поля на расширение или с расширения того же номера и типа не безопасно.
  • Изменение поля между string и bytes не безопасно.
  • Изменение поля между типом сообщения и bytes не безопасно.
  • Изменение любого поля с optional на repeated не безопасно.
  • Изменение поля между map<K, V> и соответствующим полем repeated сообщения не безопасно.
  • Перемещение полей в существующий oneof не безопасно.

Безопасные для JSON Wire изменения

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

Обратите внимание, что почти все безопасные для провода изменения могут быть критическим изменением для кода приложения. Например, добавление значения в предсуществующее enum будет критическим изменением компиляции для любого кода с исчерпывающим switch по этому enum. По этой причине Google может избегать внесения некоторых из этих типов изменений в публичные сообщения. AIP содержат рекомендации о том, какие из этих изменений безопасно вносить там.

  • Изменение одного поля optional на член нового oneof безопасно.
  • Изменение oneof, который содержит только одно поле, на поле optional безопасно.
  • Изменение поля между любыми из int32, sint32, sfixed32, fixed32 безопасно.
  • Изменение поля между любыми из int64, sint64, sfixed64, fixed64 безопасно.
  • Изменение номера поля безопасно (поскольку номера полей не используются в формате ProtoJSON), но все же настоятельно не рекомендуется, поскольку это очень небезопасно в двоичном формате передачи.
  • Добавление значений в enum безопасно, если "Выдавать значения enum как целые числа" установлено на всех relevant клиентах (см. опции)

Совместимые с JSON Wire изменения (Условно безопасные)

В отличие от безопасных для провода изменений, совместимые с проводом означают, что одни и те же данные могут быть разобраны как до, так и после данного изменения. Однако клиент, который читает его, получит потерю данных при такой форме изменения. Например, изменение int32 на int64 является совместимым изменением, но если значение больше INT32_MAX записано, клиент, который читает его как int32, отбросит старшие биты.

Вы можете вносить совместимые изменения в вашу схему только если вы тщательно управляете развертыванием в вашей системе. Например, вы можете изменить int32 на int64, но обеспечить, чтобы вы продолжали записывать только допустимые значения int32 до тех пор, пока новая схема не будет развернута на всех конечных точках, и затем начать записывать большие значения после этого.

Совместимые, но с проблемами обработки неизвестных полей

В отличие от двоичного формата передачи, реализации ProtoJSON обычно не распространяют неизвестные поля. Это означает, что добавление к схемам обычно совместимо, но приведет к сбоям разбора, если клиент, использующий старую схему, наблюдает новое содержимое.

Это означает, что вы можете добавлять к вашей схеме, но вы не можете безопасно начать записывать их, пока не узнаете, что схема была развернута на relevant клиенте или сервере (или что relevant клиенты установили флаг Игнорировать Неизвестные Поля, обсуждаемый ниже).

  • Добавление и удаление полей считается совместимым с этой оговоркой.
  • Удаление значений enum считается совместимым с этой оговоркой.

Совместимые, но потенциально с потерей

  • Изменение между любыми из 32-битных целых чисел (int32, uint32, sint32, sfixed32, fixed32) и любыми из 64-битных целых чисел ( int64, uint64, sint64, sfixed32) является совместимым изменением.
    • Если число разобрано из провода, которое не помещается в соответствующий тип, вы получите тот же эффект, как если бы вы привели число к этому типу в C++ (например, если 64-битное число читается как int32, оно будет усечено до 32 бит).
    • В отличие от двоичного формата передачи, bool не совместим с целыми числами.
    • Обратите внимание, что типы int64 по умолчанию заключаются в кавычки, чтобы избежать потери точности при обработке как double или число JavaScript, а 32-битные типы по умолчанию без кавычек. Соответствующие реализации будут принимать оба случая для всех целочисленных типов, но несоответствующие реализации могут неправильно обработать этот случай и не обработать int32 в кавычках или int64 без кавычек, что может сломаться при этом изменении.
  • enum может быть условно совместим с string
    • Если любой клиент использует флаг "enums-as-ints", то enums будут вместо этого совместимы с целочисленными типами.

Уточнения RFC 3339

Метки времени

Метки времени ProtoJSON используют формат метки времени RFC 3339. К сожалению, некоторая неоднозначность в спецификации RFC 3339 создала несколько крайних случаев, где различные другие реализации RFC 3339 не согласны, является ли формат законным или нет.

RFC 3339 намеревается объявить строгое подмножество формата ISO-8601, и некоторая дополнительная неоднозначность была создана, поскольку RFC 3339 был опубликован в 2002 году, а затем ISO-8601 был пересмотрен без соответствующих пересмотров RFC 3339.

Наиболее заметно, ISO-8601-1988 содержит эту заметку:

В представлениях даты и времени строчные символы могут использоваться, когда заглавные символы недоступны.

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

ISO-8601-2019 не содержит соответствующей заметки и однозначно, что строчные буквы не разрешены.

Это создало некоторую путаницу для всех библиотек, которые заявляют, что они поддерживают RFC 3339: сегодня RFC 3339 заявляет, что это профиль ISO-8601, но содержит уточняющую заметку, ссылающуюся на текст, который отсутствует в последней спецификации ISO-8601.

Спецификация ProtoJSON принимает решение, что формат метки времени - это более строгое определение "RFC 3339 как профиль ISO-8601-2019". Некоторые реализации Protobuf могут не соответствовать, используя реализацию разбора метки времени, которая реализована как "RFC 3339 как профиль ISO-8601-1988", которая будет принимать несколько дополнительных крайних случаев.

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

Длительности

RFC 3339 также определяет формат длительности, но, к сожалению, формат длительности RFC 3339 не имеет никакого способа выразить разрешение меньше секунды.

Кодировка длительности ProtoJSON напрямую вдохновлена представлением dur-seconds RFC 3339, но она способна кодировать наносекундную точность. Для целого числа секунд два представления могут совпадать (как 10s), но длительности ProtoJSON принимают дробные значения, и соответствующие реализации должны точно представлять наносекундную точность (как 10.500000001s).

Опции JSON

Соответствующая реализация protobuf JSON может предоставлять следующие опции:

  • Всегда выдавать поля без присутствия: Поля, которые не поддерживают присутствие и которые имеют свое значение по умолчанию, опускаются по умолчанию в выводе JSON (например, целое число с неявным присутствием со значением 0, поля строк с неявным присутствием, которые являются пустыми строками, и пустые повторяющиеся и карт поля). Реализация может предоставлять опцию для переопределения этого поведения и вывода полей с их значениями по умолчанию.

    Начиная с v25.x, реализации C++, Java и Python не соответствуют, так как этот флаг влияет на поля optional proto2, но не на поля optional proto3. Исправление запланировано для будущего релиза.

  • Игнорировать неизвестные поля: Парсер protobuf JSON должен отклонять неизвестные поля по умолчанию, но может предоставлять опцию для игнорирования неизвестных полей при разборе.

  • Использовать имя поля proto вместо имени lowerCamelCase: По умолчанию принтер protobuf JSON должен преобразовывать имя поля в lowerCamelCase и использовать его как имя JSON. Реализация может предоставлять опцию использовать имя поля proto как имя JSON вместо этого. Парсеры protobuf JSON обязаны принимать как преобразованное имя lowerCamelCase, так и имя поля proto.

  • Выдавать значения enum как целые числа вместо строк: Имя значения enum используется по умолчанию в выводе JSON. Может быть предоставлена опция использовать числовое значение значения enum вместо этого.