ProtoJSON 格式
Protobuf 支援規範的 JSON 編碼,從而更容易與不支援標準 protobuf 二進位制線格式的系統共享資料。
此頁面指定了格式,但 Protobuf 一致性測試套件涵蓋了定義符合 ProtoJSON 解析器的許多額外邊界情況,此處未詳盡說明。
格式的非目標
無法表示某些 JSON 模式
ProtoJSON 格式旨在成為可在 Protobuf 模式語言中表達的模式的 JSON 表示。
將許多預先存在的 JSON 模式表示為 Protobuf 模式並使用 ProtoJSON 進行解析可能是可能的,但它並非旨在能夠表示任意 JSON 模式。
例如,在 Protobuf 模式中無法表達寫入在 JSON 模式中常見的型別,例如 number[][] 或 number|string。
可以使用 google.protobuf.Struct 和 google.protobuf.Value 型別將任意 JSON 解析為 Protobuf 模式,但這些型別只允許您將值捕獲為無模式的無序鍵值對映。
不如二進位制線格式高效
ProtoJSON 格式不如二進位制線格式高效,也永遠不會。
轉換器使用更多的 CPU 來編碼和解碼訊息,並且(除了極少數情況)編碼訊息佔用更多的空間。
沒有二進位制線格式那樣好的模式演進保證
ProtoJSON 格式不支援未知欄位,並且它將欄位和列舉值名稱放入編碼訊息中,這使得以後更改這些名稱變得更加困難。刪除欄位是一個破壞性更改,會觸發解析錯誤。
有關更多詳細資訊,請參閱下面的JSON 線安全。
格式說明
每種型別的表示
下表顯示了資料在 JSON 檔案中的表示方式。
| Protobuf 型別 | JSON | JSON 示例 | 說明 |
|---|---|---|---|
| message | object | {"fooBar": v, "g": null, ...} | 生成 JSON 物件。 鍵被序列化為欄位名的 lowerCamelCase。有關欄位名到物件鍵對映的更多特殊情況,請參閱欄位名。 知名型別有特殊的表示,如知名型別表中所述。
|
| enum | string | "FOO_BAR" | 使用 proto 中指定的列舉值名稱。解析器接受列舉名稱和整數值。 |
| map<K,V> | object | {"k": v, ...} | 所有鍵都轉換為字串(JSON 中的物件鍵只能是字串)。 |
| repeated V | array | [v, ...] | |
| 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)在帶引號和不帶引號的形式中都接受。有關 int64 使用字串的原因,請參閱int64 的字串。 |
| float, double | number | 1.1, -10.0, 0, "NaN", "Infinity" | JSON 值將是一個數字或特殊字串值 "NaN"、"Infinity" 和 "-Infinity" 之一。接受數字或字串。空字串無效。也接受指數表示法。 |
知名型別
google.protobuf 包中的某些訊息在 JSON 中表示時具有特殊表示。
google.protobuf 包之外的任何訊息型別都沒有特殊的 ProtoJSON 處理;例如,google.types 包中的型別以中性表示表示。
| 訊息型別 | JSON | JSON 示例 | 說明 |
|---|---|---|---|
| Any | object | {"@type": "url", "f": v, ... } | 參見Any |
| 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”是必需的。這不是RFC 3339“持續時間”格式(參見持續時間以進行澄清)。 |
| Struct | object | { ... } | 任何 JSON 物件。參見struct.proto。 |
| 包裝器型別 | 各種型別 | 2, "2", "foo", true, "true", null, 0, ... | 包裝器在 JSON 中使用與包裝的原始型別相同的表示,只是允許並保留null在資料轉換和傳輸期間。 |
| FieldMask | string | "f.fooBar,h" | 參見field_mask.proto。 |
| ListValue | array | [foo, bar, ...] | |
| 值 | value | 任何 JSON 值。詳情請檢視google.protobuf.Value。 | |
| NullValue | null | JSON 空。 空解析行為的特殊情況。 | |
| Empty | object | {} (未特殊處理) | 一個空的 JSON 物件 |
欄位名作為 JSON 鍵
訊息欄位名對映到 lowerCamelCase 作為 JSON 物件鍵。如果指定了 json_name 欄位選項,則將使用指定的值作為鍵。
解析器接受 lowerCamelCase 名稱(或 json_name 選項指定的名稱)和原始 proto 欄位名。這允許序列化器選項選擇使用原始欄位名進行列印(參見JSON 選項),並使生成的輸出仍然能被所有規範解析器解析。
\0 (nul) 不允許出現在 json_name 值中。有關更多資訊,請參見json_name 的更嚴格驗證。請注意,\0 仍然被視為 string 欄位值中的合法字元。
存在性和預設值
從協議緩衝區生成 JSON 編碼輸出時,如果欄位支援存在性,則只有當相應的 hazzer 返回 true 時,序列化器才必須發出欄位值。
如果欄位不支援欄位存在性且具有預設值(例如任何空重複欄位),則序列化器應將其從輸出中省略。實現可以提供選項以在輸出中包含具有預設值的欄位。
空值
序列化器不應發出 null 值。
解析器接受 null 作為任何欄位的合法值,具有以下行為:
- 任何鍵有效性檢查仍應進行(不允許未知欄位)。
- 該欄位應保持未設定狀態,就好像它根本不存在於輸入中一樣(hazzers 在適用時仍應返回 false)。
這意味著隱式存在欄位的 null 值將與該欄位的預設值的行為完全相同,因為這些欄位沒有 hazzer。例如,重複欄位的 null 或 [] 值將導致鍵驗證檢查,但兩者在其他方面都將與該欄位根本不存在於 JSON 中一樣。
重複欄位中不允許使用 null 值。
google.protobuf.NullValue 是此行為的一個特殊例外:null 被作為此型別的哨兵存在值處理,因此此型別的欄位必須由序列化器和解析器根據標準存在行為處理。此行為相應地允許 google.protobuf.Struct 和 google.protobuf.Value 無損地往返任意 JSON。
重複值
序列化器絕不能在同一個 JSON 物件中多次序列化同一個欄位,也不能在同一個 oneof 中序列化多個不同的 case。
解析器應接受重複的相同欄位,並應保留提供的最後一個值。這也適用於同一欄位名的“替代拼寫”。
如果實現無法維護有關欄位順序的必要資訊,則更傾向於拒絕具有重複鍵的輸入,而不是讓任意值獲勝。在某些實現中,維護物件欄位順序可能不切實際或不可行,因此強烈建議系統儘可能避免依賴 ProtoJSON 中重複欄位的特定行為。
超出範圍的數值
解析數值時,如果從線路上解析出的數字不符合相應的型別,解析器應解析失敗。
這包括 uint32 的任何負數,以及 int32 小於 INT_MIN 或大於 INT_MAX 的數字。
整數型別欄位不允許帶非零小數部分的數值。允許零小數部分。例如,1.0 對於 int32 欄位是有效的,但 1.5 無效。
int64 的字串
不幸的是,json.org 規範沒有提及數字的預期精度限制。許多實現遵循 JSON 派生自的原始 JS 行為,並將所有數字解釋為 binary64(雙精度),如果數字是大於 2**53 的整數,則會靜默丟失。其他實現可能支援無限精度大整數、int64s 甚至具有無限小數精度的大浮點數。
這造成了一種情況,即如果 JSON 包含一個不能精確表示為雙精度的數字,不同的解析器將以不同的方式表現,包括許多語言中的靜默精度損失。
為了避免這些問題,ProtoJSON 序列化器將 int64s 作為字串發出,以確保任何實現都不會因大 int64s 而發生精度損失。
當解析一個裸數字並期望 int64 時,即使相應的語言的內建 JSON 解析器支援將 JSON 數字解析為大整數,實現也應將該值強制轉換為雙精度。這確保了對相同資料的一致解釋,無論使用何種語言。
這種設計遵循了在優先考慮互操作性時如何處理 JSON 中大數字的既定最佳實踐,包括
RFC8259 包含一條註釋,指出旨在實現良好互操作性的軟體應僅假定所有數字都是雙精度。
OpenAPI int64 文件建議在需要超過 2**53 的精度時使用 JSON 字串而不是數字。
Any
普通訊息
對於任何不是具有特殊 JSON 表示的知名型別的訊息,Any 中包含的訊息將被轉換為一個 JSON 物件,並插入一個額外的 "@type" 欄位,該欄位包含在 Any 上設定的 type_url。
例如,如果您有此訊息定義
package x;
message Child { int32 x = 1; string y = 2; }
當一個 Child 例項被打包到 Any 中時,其 JSON 表示為
{
"@type": "type.googleapis.com/x.Child",
"x": 1,
"y": "hello world"
}
特殊處理的知名型別
如果 Any 包含具有特殊 JSON 對映的知名型別,則訊息將轉換為特殊表示並設定為鍵為“value”的欄位。
例如,表示 3.1 秒的 google.protobuf.Duration 在特殊情況處理中將表示為字串 "3.1s"。當該 Duration 被打包到 Any 中時,它將序列化為:
{
"@type": "type.googleapis.com/google.protobuf.Duration",
"value": "3.1s"
}
具有特殊 JSON 編碼的訊息型別包括
google.protobuf.Anygoogle.protobuf.BoolValuegoogle.protobuf.BytesValuegoogle.protobuf.DoubleValuegoogle.protobuf.Durationgoogle.protobuf.FieldMaskgoogle.protobuf.FloatValuegoogle.protobuf.Int32Valuegoogle.protobuf.Int64Valuegoogle.protobuf.ListValuegoogle.protobuf.StringValuegoogle.protobuf.Structgoogle.protobuf.Timestampgoogle.protobuf.UInt32Valuegoogle.protobuf.UInt64Valuegoogle.protobuf.Value
請注意,google.protobuf.Empty 不被視為具有任何特殊的 JSON 對映;它只是一個具有零欄位的普通訊息。這意味著打包到 Any 中的 Empty 的預期表示是 {"@type": "type.googleapis.com/google.protobuf.Empty"},而不是 {"@type": "type.googleapis.com/google.protobuf.Empty", "value": {}}。
ProtoJSON 線安全
使用 ProtoJSON 時,只有某些模式更改在分散式系統中是安全的。這與應用於二進位制線格式的相同概念形成對比。
JSON 線不安全更改
線不安全更改是指如果您使用新模式的解析器解析使用舊模式序列化的資料(反之亦然),將導致中斷的模式更改。您幾乎不應該進行這種形式的模式更改。
- 將欄位更改為或從相同編號和型別的擴充套件不是安全的。
- 在
string和bytes之間更改欄位是不安全的。 - 在訊息型別和
bytes之間更改欄位是不安全的。 - 將任何欄位從
optional更改為repeated是不安全的。 - 在
map<K, V>和相應的repeated訊息欄位之間更改欄位是不安全的。 - 將欄位移入一個現有的
oneof是不安全的。
JSON 線安全更改
線路安全的變更是指完全安全地演進模式,而不會有資料丟失或新的解析失敗的風險。
請注意,幾乎所有線安全更改都可能對應用程式程式碼造成破壞性更改。例如,向現有列舉新增值將導致任何對該列舉進行窮盡 switch 的程式碼編譯失敗。因此,Google 可能會避免對公共訊息進行這些型別的更改。AIP 包含有關在何處進行這些更改是安全的指導。
- 將單個
optional欄位更改為新的oneof的成員是安全的。 - 將僅包含一個欄位的
oneof更改為optional欄位是安全的。 - 在
int32、sint32、sfixed32、fixed32之間更改欄位是安全的。 - 在
int64、sint64、sfixed64、fixed64之間更改欄位是安全的。 - 更改欄位編號是安全的(因為 ProtoJSON 格式不使用欄位編號),但仍然強烈不建議這樣做,因為它在二進位制線格式中非常不安全。
- 如果所有相關客戶端都設定了“將列舉值作為整數發出”(請參閱選項),則向列舉新增值是安全的
JSON 線相容更改(條件安全)
與線安全更改不同,線相容意味著在給定更改之前和之後都可以解析相同的資料。但是,讀取它的客戶端在這種形狀的更改下將獲得有損資料。例如,將 int32 更改為 int64 是一個相容的更改,但如果寫入的值大於 INT32_MAX,則將其作為 int32 讀取的客戶端將丟棄高位。
您只能在仔細管理系統部署時對模式進行相容更改。例如,您可以將 int32 更改為 int64,但要確保您繼續只寫入合法的 int32 值,直到新模式部署到所有端點,然後才開始寫入更大的值。
相容但存在未知欄位處理問題
與二進位制線格式不同,ProtoJSON 實現通常不傳播未知欄位。這意味著新增到模式通常是相容的,但如果使用舊模式的客戶端觀察到新內容,則會導致解析失敗。
這意味著您可以新增到模式,但在您知道模式已部署到相關客戶端或伺服器(或相關客戶端設定了“忽略未知欄位”標誌,如下所述)之前,您不能安全地開始寫入它們。
- 新增和刪除欄位在此注意事項下被認為是相容的。
- 刪除列舉值在此注意事項下被認為是相容的。
相容但可能丟失
- 在任何 32 位整數(
int32、uint32、sint32、sfixed32、fixed32)和任何 64 位整數(int64、uint64、sint64、sfixed32)之間進行更改是相容的更改。- 如果從線路解析出的數字不符合相應的型別,則會發生解析失敗。
- 與二進位制線格式不同,
bool與整數不相容。 - 請注意,int64 和 uint64 型別預設加引號,以避免在作為雙精度或 JavaScript 數字處理時出現精度損失,而 32 位型別預設不加引號。符合規範的解析器將接受所有整數型別的帶引號或不帶引號,但非符合規範的實現可能會錯誤處理此情況,不處理帶引號的 int32 或不帶引號的 int64,因此應謹慎。
enum可能有條件地與string相容- 如果任何客戶端使用“enums-as-ints”標誌,則列舉將改為與整數型別相容。
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 配置檔案”,這將接受一些額外的邊緣情況。
為了保持一致的互操作性,解析器應儘可能只接受更嚴格的子集格式。當使用接受更寬鬆定義的非符合規範實現時,強烈避免依賴接受額外的邊緣情況。
持續時間
RFC 3339 也定義了持續時間格式,但不幸的是,RFC 3339 持續時間格式無法表達亞秒解析度。
ProtoJSON 持續時間編碼直接受 RFC 3339 dur-seconds 表示的啟發,但它能夠編碼納秒精度。對於整數秒數,兩種表示可能匹配(如 10s),但 ProtoJSON 持續時間接受小數,並且符合規範的實現必須精確表示納秒精度(如 10.500000001s)。
JSON 選項
符合規範的 protobuf JSON 實現可以提供以下選項:
始終發出不帶存在性的欄位:預設情況下,不支援存在性且具有預設值的欄位在 JSON 輸出中被省略(例如,隱式存在的整數值為 0、隱式存在的空字串欄位以及空的重複欄位和對映欄位)。實現可以提供一個選項來覆蓋此行為並輸出具有其預設值的欄位。
截至 v25.x,C++、Java 和 Python 實現不符合規範,因為此標誌影響 proto2
optional欄位,但不影響 proto3optional欄位。計劃在未來的版本中修復。忽略未知欄位:protobuf JSON 解析器預設應拒絕未知欄位,但可以提供一個選項以在解析時忽略未知欄位。
使用 proto 欄位名而不是 lowerCamelCase 名稱:預設情況下,protobuf JSON 印表機應將欄位名轉換為 lowerCamelCase 並將其用作 JSON 名稱。實現可以提供一個選項來使用 proto 欄位名作為 JSON 名稱。Protobuf JSON 解析器需要同時接受轉換後的 lowerCamelCase 名稱和 proto 欄位名。
將列舉值作為整數而不是字串發出:列舉值的名稱預設在 JSON 輸出中使用。可以提供一個選項來改用列舉值的數值。