ProtoJSON 形式

Protobuf を JSON に変換するユーティリティの使用方法について説明します。

Protobuf は JSON で正規のエンコーディングをサポートしており、標準の protobuf バイナリワイヤー形式をサポートしていないシステムとのデータ共有を容易にします。

このページでは形式を規定していますが、準拠する ProtoJSON パーサーを定義する追加の境界ケースについては、Protobuf Conformance Test Suite でカバーされており、ここでは詳細に説明されていません。

形式の非目標

一部の JSON スキーマを表現できません

ProtoJSON 形式は、Protobuf スキーマ言語で表現できるスキーマの JSON 表現として設計されています。

既存の多くの JSON スキーマを Protobuf スキーマとして表現し、ProtoJSON を使用してパースできる可能性がありますが、任意の JSON スキーマを表現できるように設計されているわけではありません。

たとえば、`number[][]` や `number|string` のような JSON スキーマでよくある型を Protobuf スキーマで表現する方法はありません。

`google.protobuf.Struct` および `google.protobuf.Value` 型を使用して、任意の JSON を Protobuf スキーマにパースすることは可能ですが、これらは値をスキーマレスな順序付けられていないキーと値のマップとしてキャプチャすることしかできません。

バイナリワイヤー形式ほど効率的ではありません

ProtoJSON 形式はバイナリワイヤー形式ほど効率的ではなく、これからもそうなることはありません。

コンバータはメッセージのエンコードとデコードにより多くの CPU を使用し、(まれなケースを除いて)エンコードされたメッセージはより多くのスペースを消費します。

バイナリワイヤー形式ほどスキーマ進化の保証が優れていません

ProtoJSON 形式は未知のフィールドをサポートしておらず、フィールド名と enum 値名をエンコードされたメッセージに入れるため、後でこれらの名前を変更することがはるかに困難になります。フィールドを削除すると、パースエラーを引き起こす破壊的な変更となります。

詳細については、以下のJSON ワイヤー安全性を参照してください。

形式の説明

各型の表現

以下の表は、JSON ファイルでのデータの表現方法を示しています。

ProtobufJSONJSON の例注釈
メッセージオブジェクト{"fooBar": v, "g": null, ...}JSON オブジェクトを生成します。メッセージフィールド名は lowerCamelCase にマッピングされ、JSON オブジェクトのキーになります。`json_name` フィールドオプションが指定されている場合、指定された値が代わりにキーとして使用されます。パーサーは、lowerCamelCase 名(または `json_name` オプションで指定されたもの)と元の proto フィールド名の両方を受け入れます。`null` はすべてのフィールド型で受け入れられる値であり、フィールドを未設定のままにします。`\0 (nul)` は `json_name` 値内で使用できません。その理由については、json_name のより厳密な検証を参照してください。
enumstring"FOO_BAR"proto で指定された enum 値の名前が使用されます。パーサーは enum 名と整数値の両方を受け入れます。
map<K,V>オブジェクト{"k": v, ...}すべてのキーは文字列に変換されます(JSON 仕様のキーは文字列のみにできます)。
repeated V配列[v, ...]`null` は空のリスト `[]` として受け入れられます。
booltrue, falsetrue, false
stringstring"Hello World!"
bytesbase64 文字列"YWJjMTIzIT8kKiYoKSctPUB+"JSON 値は、標準の base64 エンコーディングとパディングを使用して文字列としてエンコードされたデータになります。標準または URL セーフな base64 エンコーディング(パディングあり/なし)の両方が受け入れられます。
int32, fixed32, uint32数値1, -10, 0JSON 値は 10 進数になります。数値または文字列のどちらも受け入れられます。空の文字列は無効です。指数表記(`1e2` など)は、引用符付きと引用符なしの両方で受け入れられます。
int64, fixed64, uint64string"1", "-10"JSON 値は 10 進数文字列になります。数値または文字列のどちらも受け入れられます。空の文字列は無効です。指数表記(`1e2` など)は、引用符付きと引用符なしの両方で受け入れられます。
float, double数値1.1, -10.0, 0, "NaN", "Infinity"JSON 値は数値、または特殊な文字列値 "NaN"、"Infinity"、"-Infinity" のいずれかになります。数値または文字列のどちらも受け入れられます。空の文字列は無効です。指数表記も受け入れられます。
Anyオブジェクト{"@type": "url", "f": v, ... }`Any` がこのテーブルに特別な JSON マッピングを持つ既知の型(たとえば `google.protobuf.Duration`)を含む場合、`{"@type": xxx, "value": yyy}` のように変換されます。それ以外の場合、値は通常の JSON オブジェクトに変換され、メッセージの型を示す URL の値を持つ追加の `"@type"` フィールドが挿入されます。
Timestampstring"1972-01-01T10:00:20.021Z"RFC 3339 を使用します(明確化を参照)。生成される出力は常に Z-正規化され、0、3、6、または 9 桁の小数点以下を使用します。「Z」以外のオフセットも受け入れられます。
Durationstring"1.000340012s", "1s"生成される出力は、必要な精度に応じて常に 0、3、6、または 9 桁の小数点以下を含み、その後にサフィックス "s" が続きます。ナノ秒の精度に収まる限り、任意の小数点以下(なしでも可)が受け入れられ、サフィックス "s" が必須です。
Structオブジェクト{ ... }任意の JSON オブジェクト。`struct.proto` を参照してください。
ラッパー型さまざまな型2, "2", "foo", true, "true", null, 0, ...ラッパーは、`null` が許可され、データ変換および転送中に保持される点を除き、ラップされたプリミティブ型と同じ JSON 表現を使用します。
FieldMaskstring"f.fooBar,h"`field_mask.proto` を参照してください。
ListValue配列[foo, bar, ...]
任意の JSON 値。google.protobuf.Value を参照して詳細を確認してください。
NullValuenullJSON null。 [null 解析動作](#null-values) の特殊なケースです。
オブジェクト{}空の JSON オブジェクト

プレゼンスとデフォルト値

プロトコルバッファから JSON エンコードされた出力を生成する場合、フィールドがプレゼンスをサポートしている場合、シリアライザーは対応するハッサーが true を返す場合に限り、フィールド値を出力しなければなりません。

フィールドがフィールドプレゼンスをサポートしておらず、デフォルト値を持つ場合(例:空の repeated フィールド)、シリアライザーはそれを出力から省略すべきです。実装は、デフォルト値を持つフィールドを出力に含めるオプションを提供する場合があります。

null 値

シリアライザーは `null` 値を出力すべきではありません。

パーサーは、以下の動作で任意のフィールドの有効な値として `null` を受け入れるべきです。

  • キーの有効性チェックは引き続き行われるべきです(未知のフィールドは許可されません)。
  • フィールドは未設定のままであるべきです。まるで入力に全く存在しなかったかのように扱われるべきです(該当する場合、ハッサーは false を返す必要があります)。

`null` 値は繰り返しフィールド内では許可されません。

`google.protobuf.NullValue` はこの動作の特別な例外です: `null` はこの型の sentinel-present 値として扱われるため、この型のフィールドは標準のプレゼンス動作に従ってシリアライザーとパーサーによって処理される必要があります。この動作により、`google.protobuf.Struct` および `google.protobuf.Value` は任意の JSON をロスレスにラウンドトリップできるようになります。

重複値

シリアライザーは、同じフィールドを複数回シリアライズしたり、同じ JSON オブジェクト内の同じ oneof の複数の異なるケースをシリアライズしたりしてはなりません。

パーサーは、同じフィールドが重複していることを受け入れるべきであり、最後に指定された値が保持されるべきです。これは、同じフィールド名の「代替スペル」にも適用されます。

実装がフィールド順序に関する必要な情報を維持できない場合、任意の値を優先するよりも、重複するキーを含む入力を拒否する方が望ましいです。一部の実装では、オブジェクトのフィールド順序を維持することが非現実的または実現不可能である場合があるため、ProtoJSON で重複フィールドの特定の動作に依存することは、可能な限り避けることを強くお勧めします。

範囲外の数値

数値解析時、ワイヤーからパースされた数値が対応する型に適合しない場合、パーサーはその値を適切な型に強制変換すべきです。これは C++ や Java の単純なキャストと同じ動作です(たとえば、int32 フィールドとして 2^32 より大きい数値が読み取られた場合、32 ビットに切り捨てられます)。

ProtoJSON ワイヤー安全性

ProtoJSON を使用する場合、分散システムで安全に行えるスキーマ変更は一部に限られます。これは、バイナリワイヤー形式に適用される同じ概念とは対照的です。

JSON ワイヤー非互換の変更

ワイヤー非互換な変更とは、古いスキーマを使用してシリアライズされたデータを新しいスキーマを使用するパーサーでパースした場合(またはその逆)、壊れるスキーマ変更のことです。このようなスキーマ変更はほとんど行うべきではありません。

  • フィールドを同じ番号と型の拡張機能に変更したり、その逆を行ったりすることは安全ではありません。
  • フィールドを `string` と `bytes` の間で変更することは安全ではありません。
  • フィールドをメッセージ型と `bytes` の間で変更することは安全ではありません。
  • 任意のフィールドを `optional` から `repeated` に変更することは安全ではありません。
  • フィールドを `map<K, V>` と対応する `repeated` メッセージフィールドの間で変更することは安全ではありません。
  • フィールドを既存の`oneof`に移動することは安全ではありません。

JSON ワイヤー互換の変更

ワイヤーセーフな変更とは、データの損失や新たなパース失敗のリスクなしに、スキーマをこの方法で進化させることが完全に安全な変更です。

ほぼすべてのワイヤー互換の変更は、アプリケーションコードにとって破壊的な変更となる可能性があることに注意してください。たとえば、既存の enum に値を追加すると、その enum の網羅的な switch を持つコードはコンパイルエラーになります。このため、Google はパブリックメッセージに対してこれらの種類の変更の一部を行わない場合があります。AIP には、これらの変更のうちどれが安全であるかについてのガイダンスが含まれています。

  • 単一の `optional` フィールドを**新しい** `oneof` のメンバーに変更することは安全です。
  • 1 つのフィールドのみを含む `oneof` を `optional` フィールドに変更することは安全です。
  • `int32`、`sint32`、`sfixed32`、`fixed32` のいずれかの間でフィールドを変更することは安全です。
  • `int64`、`sint64`、`sfixed64`、`fixed64` のいずれかの間でフィールドを変更することは安全です。
  • フィールド番号の変更は安全です(ProtoJSON 形式ではフィールド番号は使用されないため)が、バイナリワイヤー形式では非常に安全ではないため、依然として強く推奨されません。
  • 関連するすべてのクライアントで「enum 値を整数として出力」が設定されている場合、enum に値を追加することは安全です(オプションを参照)。

JSON ワイヤー互換の変更 (条件付きで安全)

ワイヤー互換の変更とは異なり、ワイヤー互換とは、特定の変更の前と後で同じデータをパースできることを意味します。ただし、この種の変更では、それを読み取るクライアントは情報が失われたデータを受け取ることになります。たとえば、int32 を int64 に変更することは互換性のある変更ですが、INT32_MAX より大きい値が書き込まれた場合、int32 として読み取るクライアントは上位ビットを破棄します。

スキーマに互換性のある変更を加えることができるのは、システムへの展開を慎重に管理する場合に限られます。たとえば、int32 を int64 に変更できますが、新しいスキーマがすべてのエンドポイントにデプロイされるまで有効な int32 値のみを書き込み続け、その後により大きな値を書き込み始めるようにする必要があります。

互換性はあるが、未知のフィールド処理の問題がある

バイナリワイヤー形式とは異なり、ProtoJSON 実装は一般に未知のフィールドを伝播しません。これは、スキーマへの追加は一般的に互換性がありますが、古いスキーマを使用しているクライアントが新しいコンテンツを検出するとパース失敗の原因となることを意味します。

これは、スキーマに追加することはできますが、関連するクライアントまたはサーバーにスキーマがデプロイされたことを確認するまで(または、関連するクライアントが以下で説明する「未知のフィールドを無視する」フラグを設定するまで)、安全に書き込みを開始できないことを意味します。

  • フィールドの追加と削除は、この注意点付きで互換性があると考えられます。
  • 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」フラグが任意のクライアントで使用されている場合、enum は代わりに整数型と互換性を持つようになります。

RFC 3339 の明確化

RFC 3339 は ISO-8601 形式の厳密なサブセットを宣言することを意図していましたが、残念ながら RFC 3339 が 2002 年に公開された後、ISO-8601 が対応する改訂なしにその後改訂されたため、いくつかの曖昧さが生じました。

特に、ISO-8601-1988 には次の注記が含まれています。

日付と時刻の表現では、大文字が利用できない場合、小文字を使用することができます。

この注記が、パーサーが一般的に小文字を受け入れるべきであることを示唆しているのか、それとも大文字を技術的に使用できない環境での代替としてのみ小文字を使用できることを示唆しているのかは曖昧です。RFC 3339 には、小文字を一般的に受け入れるべきであるという解釈を明確にすることを意図した注記が含まれています。

ISO-8601-2019 には対応する注記が含まれておらず、小文字は許可されないことが明確です。これにより、RFC 3339 をサポートすると宣言するすべてのライブラリに混乱が生じました。現在、RFC 3339 は ISO-8601 のプロファイルであると宣言していますが、最新の ISO-8601 仕様にはもはや存在しないものに関する注記が含まれています。

ProtoJSON 仕様は、タイムスタンプ形式が「ISO-8601-2019 のプロファイルとしての RFC 3339」のより厳密な定義であるという決定を下しました。一部の Protobuf 実装は、「ISO-8601-1988 のプロファイルとしての RFC 3339」として実装されたタイムスタンプ解析実装を使用しているため、非準拠である可能性があり、いくつかの追加の境界ケースを受け入れることになります。

一貫した相互運用性のために、パーサーは可能な限り厳密なサブセット形式のみを受け入れるべきです。より緩い定義を受け入れる非準拠の実装を使用する場合でも、追加の境界ケースが受け入れられることに依存することは強く避けるべきです。

JSON オプション

準拠するプロトコルバッファ JSON 実装は、以下のオプションを提供する場合があります。

  • プレゼンスなしで常にフィールドを出力: プレゼンスをサポートせず、デフォルト値を持つフィールドは、JSON 出力でデフォルトで省略されます(たとえば、値が 0 の暗黙的なプレゼンス整数、空文字列である暗黙的なプレゼンス文字列フィールド、空の繰り返しフィールドとマップフィールド)。実装は、この動作をオーバーライドして、デフォルト値を持つフィールドを出力するオプションを提供する場合があります。

    v25.x の時点では、C++、Java、Python の実装はこのフラグが proto2 の `optional` フィールドには影響するが proto3 の `optional` フィールドには影響しないため、非準拠です。将来のリリースで修正が予定されています。

  • 未知のフィールドを無視: プロトコルバッファ JSON パーサーは、デフォルトで未知のフィールドを拒否すべきですが、パース時に未知のフィールドを無視するオプションを提供する場合があります。

  • lowerCamelCase 名の代わりに proto フィールド名を使用: デフォルトでは、プロトコルバッファ JSON プリンタはフィールド名を lowerCamelCase に変換し、それを JSON 名として使用すべきです。実装は、代わりに proto フィールド名を JSON 名として使用するオプションを提供する場合があります。プロトコルバッファ JSON パーサーは、変換された lowerCamelCase 名と proto フィールド名の両方を受け入れる必要があります。

  • enum 値を文字列の代わりに整数として出力: デフォルトでは、enum 値の名前が JSON 出力で使用されます。代わりに enum 値の数値を使用するオプションが提供される場合があります。