JSON「最後不能加逗號」是不是錯誤設計?

如何看待json語法中不能加註釋的設計?www.zhihu.com圖標


小小地反對一下 @徐辰 ,JSON結尾允許多餘逗號並不會給 Parser 編寫者帶來多少麻煩。

舉例來說,rapidjson 支持多餘逗號只用了很少的代碼:


至於我個人來說,允許結尾的逗號對於手寫 JSON 時的美觀度和方便度都是有一定提升的(不會因為複製了幾行而忘了刪/忘了加逗號而報錯)比如說 C++ 里寫一個數組的話分多行時這樣就看起來很整齊(單行時結尾不加):

std::vector& points = {
{0.0, 0.0, 0.0}, {1.0, 0.0, 0.0}, {1.0, 1.0, 0.0}, {0.0, 1.0, 0.0},
{0.0, 0.0, 1.0}, {1.0, 0.0, 1.0}, {1.0, 1.0, 1.0}, {0.0, 1.0, 1.0},
// ...
};

如果 JSON 能重新設計的話,我還是希望能允許結尾加逗號的(當然不少解析器也已經實現了這種功能)。

* 當然,如果你認為 JSON 不能用來手寫的話,可以無視這個部分。


JSON 現行標準 RFC7159 [1] 和 ECMA-404 [2] 明確地定義 JSON 的功能是數據互換格式(data interchange format)。作為互換格式,本身就應該盡量簡單,避免一些可有可無的語法。我認為「最後可加逗號」算是可有可無的語法。

雖然如此,我覺得現在還是有一些可有可無的語法。例如字元串里的 	exttt{/} (斜線符)可表示為 	exttt{"/"} 也可表示為轉義形式 	exttt{"\/"} 。另外,數字範圍也是個比較大的問題,[1] 里說由實現決定,而又由於有些實現統一用雙精度來表示數字,那麼就不能處理很常見的 64 位整數。

身為 RapidJSON 的作者,希望這個庫能提供最嚴格的 JSON 語法校驗,但同時,不少使用者都想放寬限制,有不少相關討論 [3],我簡單翻譯一些擴展語法需求:

  1. 根節點可以是任意類型。(JSON 原始標準 [4] 只容許 object 和 array 作為根節點,[1] 已放寬)
  2. 單行、多行注釋
  3. 無引號的鍵(那麼是否容許空格、冒號、轉義符?)
  4. object、array 最後可加逗號
  5. 單引號字元串(JSON 標準只能用雙引號)
  6. 小數點為首的數字(JSON 標準中,小數點前必須有最少一個數字)
  7. 小數點後沒有數字(JSON 標準中,小數點後必須有最少一個數字)
  8. 十六進位數字(二進位、八進位呢)
  9. 無窮
  10. NaN

貧窮限制了我的想像力,甚至,曾經有使用者希望可以支持數字運算表達式??

即使不考慮上述的擴展語法,許多開源的 JSON 解析器/生成器也不能完全符合標準。我在 [5] 測試開源庫性能的同時,也測試了它們是否合符標準(有少部分測試高於標準要求,例如完美還原數字):

一些開源原生 JSON庫的標準合符程度測試結果

因此,個人認為 JSON 標準還是盡量簡單好一些,再複雜、再多可有可無的語法,只會令應用時出現更多問題。所以,我對題目是否定的,這不是錯誤設計,而是一個合理的設計。如果考慮把 JSON 擴展成適合用於人手編寫的格式,或許可考慮 YAML 和 JSON5 等。


更新:看到有答案說輸出 JSON 很麻煩。我只想回應:

不要手工編碼輸出 JSON!

不要手工編碼輸出 JSON!

不要手工編碼輸出 JSON!

你知道你用的語言/運行時所輸出的數字必定合符 JSON 要求么?你有處理字元串里的字元轉義么?

就算不使用 DOM,有些庫(如 RapidJSON)還提供最簡單的 SAX 風格 API:

StringBuffer s;
Writer& writer(s);

writer.StartArray();
for (int i = 1; i &<= 5; i++) writer.Int(i); writer.EndArray(); std::cout &<&< s.GetString(); // [1,2,3,4,5]

讓程序庫處理逗號,不要每次都做輪子。


[1] The JavaScript Object Notation (JSON) Data Interchange Format

[2] Standard ECMA-404

[3] Strict/Relaxed JSON syntax · Issue #36 · Tencent/rapidjson

[4] The application/json Media Type for JavaScript Object Notation (JSON)

[5] miloyip/nativejson-benchmark


可能會有人覺得這個題目莫名奇妙,但其實有時候這個末尾逗號有一定的道理。

假設你有一個列表,長這個樣子:

var list = ["a", "b", "c"]

我估計不會有誰去把它寫成:

var list = ["a", "b", "c", ]

這樣很怪,而且沒有必要。但是如果你的 list 長這個樣子:

var list = [
"a",
"b",
"c",
]

我們的 Lint 規則裡面有一條就是當你用這種風格構造數組或枚舉時,要加上最後一個逗號(Multi-line collection literals should have trailing commas)。

這樣的話對版本控制有個好處,就是當你增加一行在尾部的時候,產生的 diff 只有一行,而如果不是這種風格,則會產生三行 diff

另外一個好處只對某些語言有效果,比如 Python,末尾逗號習慣可以避免產生這樣意想不到的代碼:

list = [
"1",
"2",
"3"
"4"
]

你可能增加一行之後忘記添加逗號,導致你產生了一個:

["1", "2", "34"]

當然這只是一個風格問題,各有優劣。


當然是了,破壞一致性

忘記是哪個語言不支持數組最後一個元素後有逗號,現在加上這個功能了

json 的野雞設計還有很多,所以其實很多情況下都應該自己魔改一下設計

但是考慮到市面上還有縮進的格式和標記……你就應該知道對這個世界不能要求太多


從JSON發明初衷來看,或許不是錯誤的設計,但我覺得這是一個糟糕的設計。

我來解釋一下JSON不允許末尾逗號會帶來什麼不方便的地方,舉兩個例子

1. 拼接JSON串

在嵌入式linux環境下(可用的shell命令不多),我要把系統掛載點信息以json格式POST到伺服器上,或許這樣寫代碼最簡單

#!/bin/sh
mount_info() {
echo "["
df | sed 1d | awk "{print $1,$6}" |
while read line
do
printf " {"fs": "%s", "mount": "%s"},
" $line
done
echo "]"
}
curl xxx.xxx/xxx -H "Content-type: application/json" -X POST -d "$(mount_info)"

但是其中有一個問題就是,mount_info生成的JSON會多一個逗號,導致解析失敗。

[
{"fs": "rootfs", "mount": "/"},
{"fs": "/dev/root", "mount": "/rom"},
{"fs": "tmpfs", "mount": "/tmp"},
{"fs": "/dev/mtdblock3", "mount": "/overlay"},
{"fs": "overlayfs:/overlay", "mount": "/"},
{"fs": "tmpfs", "mount": "/dev"},
]

所以我不得不把mount_info的代碼修改為

mount_info() {
printf "["
firstline=true
df | sed 1d | awk "{print $1,$6}" |
while read line
do
if [ $firstline == true ]; then
firstline=false
else
printf ", "
fi
printf "{"fs": "%s", "mount": "%s"}" $line
done
printf "]"
}

2.生成補丁文件

假如我有一個config.json如下

[
"key": "CMXlhcHuN0mCQpitZjN6AudhJEFjimne",
"server": "192.168.1.1"
]

某一個版本開始,我需要指定埠號

[
"key": "CMXlhcHuN0mCQpitZjN6AudhJEFjimne",
"server": "192.168.1.1",
"port": 26817
]

那麼,這個修改將影響兩行,diff出來的差異也並不雅觀

@@ -1,4 +1,5 @@
[
"key": "CMXlhcHuN0mCQpitZjN6AudhJEFjimne",
- "server": "192.168.1.1"
+ "server": "192.168.1.1",
+ "port": 26817
]


我覺得不支持結尾逗號 對開發者很不友好。

經常會因為這個報錯,而且不注意的話 不好找出來。

支持逗號對於寫parser人來說,並不會費額外的很多時間。

所以我覺得是設計錯誤,或者過度設計。

對於程序開發者來說,做測試時手寫json是非常常見的。


JSON 抄襲/借鑒 JS 的時候,JS 對象就是不能有多餘逗號的。

所以其實是 JS 設計問題。


這也就是有了json5格式的其中一個原因


是。對手寫不友好,尤其是對版本控制也不友好。每追加一行數據,總是顯示一整行都發生了變化。


風格而已,python好多代碼中函數參數列表可以以逗號結尾,好處是啟用其他參數時非常方便,直接在後面寫就行了,當然這會帶來parse的額外負擔。當然也可以像C那樣把參數寫齊了,不允許參數列表以逗號結尾。JSON也是同樣的道理。



是不是錯誤設計就要看這個語言設計時的初衷了。

從人類可讀性的角度討論:

不加逗號,分行的時候不好看;加逗號,單行的時候又不好看。

所以還是 YAML 好,用前置的項目符號來表達數組內的各個元素。

因此 YAML 更易讀。

從計算機可讀性的角度討論:

其實沒差別,trailing commas 還會給 Parser 增加幾行代碼。更何況,JSON 本身就是一種數據傳輸格式,並不是用來存儲下來給人看的,所以對於需要人自己編寫的文件,不管是配置文件也好,數據字典也好,用 XML 或者 YAML 無疑是更好的選擇。


JSON 設計出來是拿來給程序做數據交換的,不是給你人肉寫的

你想要方便人寫的格式,來啊,json5/json5


這個問題叫我想起來,有個語言叫Lua,加不加逗號根據解釋器版本的不同,會產生不同的語義(產生一個nil值或者沒有)


不方便,但是談不上錯誤。

@徐辰 說寫 Parser 麻煩,其實這個麻煩的麻煩程度也就是多寫幾行,頂多十幾行代碼的事,相比其他部分根本算不上什麼麻煩。

@Geoffrey Tang 說 C 之後的很多語言都不支持這種寫法,但是反過來還不是很多語言都支持這種語法?這些語言里常見的有:C#、C++、Python、JavaScript、Rust、Scala……


一群大活人跟死語法較勁?

逗!號前置大法好!

{"a": "a"
,"b": "b"
}


明天 Github Trending:

JSON+, a JSON-like data exchange format but allows comma at the end of objects and arrays.

後天 Github Trending:

JSON++, a JSON+-like data format but allows single-quoted keys.

大後天 Github Trending:

JSON Plus3, a JSON++-like human-friendly format because it supports comments.

......

一個月後:

JSON5, a human-friendly data format that is just fricking awesome cause it-supports-anything-idiots-would-ever-want-to-write-by-their-own-hands.

(這玩意真的存在)

------

- 你們真厲害哦,我選擇用 XML,可以閉嘴了嗎?

- 那我們搞一個 JSON 和 XML 的融合體吧!(這玩意也真的存在)


最後加逗號會不會給人一種……嗯……就是後面還有一個空元素的感覺?

如果JSON最後沒有逗號,以元素結尾,就不會有任何這個意思了。

還是要按照JSON規範去產生。


設不是設計錯誤,也不是parse的問題,完全就是為了規則簡單,就像屬性名必須加引號一樣。

事實上很多解析器也是可以處理不規範格式的。


我覺得不是。因為我在給json寫文法的時候發現,最後加不加逗號其實沒什麼太大的區別,寫出來都差不多:

最後逗號可選:[Fuck{"," Fuck}[","]]

最後逗號禁止:[Fuck{"," Fuck}]

最後逗號必須:{Fuck ","}


推薦閱讀:

我知道英語很重要,也很想學英語,但總是很懶,不想背單詞怎麼辦?
為什麼大多數編程語言只有異或運算符而沒有同或運算符?
為什麼棧沒有讀取並彈出的函數?
Qt 如何打包一個軟體?

TAG:編程 | 計算機科學 | JSON |