開發者工具 · JSON 格式化程式和驗證程序
JSON 中的關鍵順序重要嗎?排序、相等和 RFC 8785
· 背景
json 標準 驗證
JSON 規範呼叫無序對象,但真正的解析器和序列化器通常保留順序,並簽章方案依賴它。這篇文章闡明了規範的內容、實現的作用以及規範化如何解決緊張局勢。
相同的資料,不同的位元組
`{"city":"Oslo","temp":4}` 和 `{"temp":4,"city":"Oslo"}` 包含相同的兩個名稱和值,但它們的來源位元組不同。縮排可以添加更多文字差異,而無需更改任何解析值。這就是為什麼「equal JSON」需要一個比較規則:您是在比較文字、解析的物件還是另一個協定定義的規範表示?
ToolAcre 可以透過使用相同的縮排格式化兩個檔案來消除空白噪音。當選擇該選項時,它還可以遞歸地對物件鍵進行排序。排序會故意更改成員順序,但不會移動數組元素,因為數組位置代表資料。普通格式和此選用排序都不會產生 RFC 8785 規格 JSON,因此輸出不得取代為指定的簽章格式。
RFC 8259 所說的 — 物件是名稱 /value 對的無序集合,並實作可能會暴露順序或不暴露順序
RFC 8259 將物件描述為 name/value 對的無序集合。因此,將成員順序視為普通 JSON 物件意義的軟體依賴於該抽像模型之外的行為。陣列是明確排序的,因此 `["draft","final"]` 不能與 `["final","draft"]` 互換。物件順序和數組順序絕不能用相同的規則規範化。
RFC 也指出,函式庫的不同之處在於它們是否會向呼叫者公開成員排序。這個警告對於可移植設計來說已經足夠了:不要透過將一個物件成員放在另一個物件成員之前來編碼優先順序或順序。如果順序很重要,請用陣列或明確欄位表示它。顯示穩定順序的格式化程式對人類來說很方便,但它不會將位置轉換為物件的標準級屬性。
這個格式化程式實際上做了什麼
停用排序後,ToolAcre 會解析檔案並序列化產生的 JavaScript 值。輸出遵循 JavaScript 屬性枚舉行為,而不是逐字節保留原始令牌流。大多數普通字串鍵以熟悉的順序出現,而類似整數索引的名稱可以在其他名稱之前發出。數字拼字和轉義選擇也可以在重新序列化期間標準化。
啟用排序後,格式化程式會建立新對象,其自己的鍵在每個嵌套對像中按字母順序排列。對於 `{"z":{"b":1,"a":2},"items":[{"d":4,"c":3},"x"]}`,物件名稱變成 `items`、`z`;巢狀物件名稱也已排序;且陣列仍包含 `"x"` 之前的物件。對數組內的物件進行排序並不意味著對數組本身進行排序。
當位元組順序很重要時
只要進程消耗精確的位元組而不是抽象值,文字順序就很重要。當成員移動或空白變更時,檔案雜湊、快取金鑰、數位簽章或基於行的差異會發生變化。這與無序物件模型並不矛盾;這意味著周圍的進程選擇了位元組表示作為其輸入的一部分。表示規則必須是明確的和共享的。
對於例行審查,一致的縮排和可選的字母排序可以使變更更容易看到。對於密碼學或協議工作來說,「看起來穩定」並不是一個契約。生產者和驗證者必須在散列或簽署之前使用其協議所需的確切規範化演演算法。如果沒有指定演演算法,則不要假設 ToolAcre 的輸出將與跨版本、執行時或邊緣情況值的另一個序列化器相符。
為什麼鍵排序不是 RFC 8785
RFC 8785 定義了 JSON 規範化方案,用於從相容資料產生可重複位元組。它的工作範圍比按字母順序放置按鍵更廣泛。它指定確定性屬性排序以及字串和數字的精確序列化行為,並對輸入模型施加約束。漂亮的縮排不是規範輸出的一部分,而且區域設定感知排序不是可接受的近似值。
ToolAcre 未提出 RFC 8785 宣告。它的排序選項是分層於 `JSON.parse` 和 `JSON.stringify` 之上的可讀性功能;它不會驗證 I-JSON 前提條件或取代 RFC 的序列化規則。諸如 `1e-7` 之類的值、包含非 ASCII 字元的鍵或轉義字串可以揭示隨意排序的格式化程序和一致的規範化程序之間的差異。當需要 JCS 時,使用經過測試的 JCS 實作。
工作範例:公平比較兩份文件
將 `{"meta":{"rev":2,"owner":"Mira"},"steps":["cut","pack"]}` 與 `{"steps":["cut","pack"],"meta":{"owner":"Mira","rev":2}}` 進行比較。將兩者格式化為兩個空格並停用排序:空格變得一致,但根和巢狀成員順序仍然可以不同。解析兩者並比較它們的預期欄位以建立值層級的等效性,而不是宣告原始文字相等。
開啟遞歸鍵排序,兩個範例都以相同的物件順序渲染,而 `steps` 保留 `cut`,然後是 `pack`。這對於人類差異很有用,但它仍然是 ToolAcre 的標準化,而不是 RFC 8785 證明。如果第二個陣列是 `["pack","cut"]`,排序鍵將正確地使差異可見,因為變更陣列會變更表示的序列。
這不包括什麼
鍵排序並未為每個應用程式定義深度相等。 `JSON.parse` 接受重複的名稱,它保留最後一個值,因此格式化可以刪除一個來源包含重複的證據。 JavaScript 值中的大整數可能已經遺失了精確度。網域也可以將選定的陣列視為集合,但 ToolAcre 無法推斷該規則,因此永遠不會對陣列重新排序。
格式化程式也不比較模式、套用預設值、標準化 Unicode 或決定下游系統是否可以接受兩個數字表示形式。這些是單獨的合約。使用格式化來減少表示噪音,使用專門建構的結構比較來實現值相等,並使用指定的規範化器來獲取精確的位元組。將這些工作混合在「標準化」一詞下會導致人們對實際比較的內容產生錯誤的信心。
重點:順序對模型來說無關緊要,但對位元組來說很重要
物件成員順序在 RFC 8259 資料模型中沒有意義,而陣列順序則有意義。來源位元組仍然記錄順序和空格,因此散列、簽名和文字差異會觀察到面向值的比較可能會忽略的差異。在選擇工具之前先說明哪一層重要:文字身分、解析值等價性和協定定義的規範身分是三個不同的問題。
ToolAcre 僅間接支援前兩個工作流程:一致的格式可以澄清文字差異,遞歸字母物件鍵排序可以使人工比較更加安靜。數組永遠不會排序。結果不是 RFC 8785 規範 JSON,不應像它那樣進行簽名。當詞彙證據很重要時,保留原始輸入,特別是因為解析重複鍵只保留最後一個值。