學(xué)習(xí)完整課程請(qǐng)移步 互聯(lián)網(wǎng) Java 全棧工程師
很多項(xiàng)目使用 JSON 作為配置文件愧沟,最明顯的例子就是 npm 和 yarn 使用的 package.json 文件。當(dāng)然鲤遥,還有很多其他文件沐寺,例如 CloudFormation(最初只有 JSON,但現(xiàn)在也支持 YAML)和 composer(PHP)盖奈。
但是混坞,JSON 實(shí)際上是一種非常糟糕的配置語言。別誤會(huì)我的意思钢坦,我其實(shí)是喜歡 JSON 的究孕。它是一種相對(duì)靈活的文本格式,對(duì)于機(jī)器和人類來說都很容易閱讀场钉,而且是一種非常好的數(shù)據(jù)交換和存儲(chǔ)格式蚊俺。但作為一種配置語言懈涛,它有它的不足逛万。
為什么流行使用 JSON 作為配置語言?
將 JSON 用作配置文件有幾個(gè)方面的原因批钠,其中最大的原因可能是它很容易實(shí)現(xiàn)宇植。很多編程語言的標(biāo)準(zhǔn)庫都支持 JSON,開發(fā)人員或用戶可能已經(jīng)很熟悉 JSON埋心,所以不需要學(xué)習(xí)新的配置格式就可以使用那些產(chǎn)品≈赣簦現(xiàn)在幾乎所有的工具都提供 JSON 支持,包括語法突出顯示拷呆、自動(dòng)格式化闲坎、驗(yàn)證工具等疫粥。
這些都是很好的理由,但這種無處不在的格式其實(shí)不適合用作配置腰懂。
JSON 的問題
缺乏注釋
注釋對(duì)于配置語言而言絕對(duì)是一個(gè)重要的功能梗逮。注釋可用于標(biāo)注不同的配置選項(xiàng)、解釋為什么要配置成特定的值绣溜,更重要的是慷彤,在使用不同的配置進(jìn)行測(cè)試和調(diào)試時(shí)需要臨時(shí)注釋掉部分配置。當(dāng)然怖喻,如果只是把 JSON 當(dāng)作是一種數(shù)據(jù)交換格式底哗,那么就不需要用到注釋。
我們可以通過一些方法給 JSON 添加注釋锚沸。一種常見的方法是在對(duì)象中使用特殊的鍵作為注釋跋选,例如“//”或“__comment”。但是哗蜈,這種語法的可讀性不高野建,并且為了在單個(gè)對(duì)象中包含多個(gè)注釋,需要為每個(gè)注釋使用唯一的鍵恬叹。David Crockford(JSON 的發(fā)明者)建議使用預(yù)處理器來刪除注釋候生。如果你的應(yīng)用程序需要使用 JSON 作為配置,那么完全沒問題绽昼,不過這確實(shí)帶來了一些額外的工作量贷币。
一些 JSON 庫允許將注釋作為輸入。例如缰泡,Ruby 的 JSON 模塊和啟用了 JsonParser.Feature.ALLOW_COMMENTS 功能的 Java Jackson 庫可以處理 JavaScript 風(fēng)格的注釋近迁。但是,這不是標(biāo)準(zhǔn)的方式菱农,而且很多編輯器無法正確處理 JSON 文件中的注釋缭付,這讓編輯它們變得更加困難。
過于嚴(yán)格
JSON 規(guī)范非常嚴(yán)格循未,這也是為什么實(shí)現(xiàn) JSON 解析器會(huì)這么簡(jiǎn)單陷猫,但在我看來,它還會(huì)影響可讀性的妖,并且在較小程度上會(huì)影響可寫性绣檬。
低信噪比
與其他配置語言相比,JSON 顯得非常嘈雜嫂粟。JSON 的很多標(biāo)點(diǎn)符號(hào)對(duì)可讀性毫無幫助娇未,況且,對(duì)象中的鍵幾乎都是標(biāo)識(shí)符星虹,所以鍵的引號(hào)其實(shí)是多余的零抬。
此外镊讼,JSON 需要使用花括號(hào)將整個(gè)文檔包圍起來,所以 JSON 是 JavaScript 的子集平夜,并在流中發(fā)送多個(gè)對(duì)象時(shí)用于界定不同的對(duì)象狠毯。但是,對(duì)于配置文件來說褥芒,最外面的大括號(hào)其實(shí)沒有任何用處嚼松。在配置文件中,鍵值對(duì)之間的逗號(hào)也是沒有必要的锰扶。通常情況下献酗,每行只有一個(gè)鍵值對(duì),所以使用換行作為分隔符更有意義坷牛。
說到逗號(hào)罕偎,JSON 居然不允許在結(jié)尾出現(xiàn)逗號(hào)。如果你需要在每個(gè)鍵值對(duì)之后使用逗號(hào)京闰,那么至少應(yīng)該接受結(jié)尾的逗號(hào)颜及,因?yàn)橛辛私Y(jié)尾的逗號(hào),在添加新條目時(shí)會(huì)更容易蹂楣,而且在進(jìn)行 commit diff 時(shí)也更清晰俏站。
長字符串
JSON 作為配置格式的另一個(gè)問題是,它不支持多行字符串痊土。如果你想在字符串中換行肄扎,必須使用 “\n” 進(jìn)行轉(zhuǎn)義,更糟糕的是赁酝,如果你想要一個(gè)字符串在文件中另起一行顯示犯祠,那就徹底沒辦法了。如果你的配置項(xiàng)里沒有很長的字符串酌呆,那就不是問題衡载。但是,如果你的配置項(xiàng)里包括了長字符串隙袁,例如項(xiàng)目描述或 GPG 密鑰痰娱,你可能不希望只是使用 “\n” 來轉(zhuǎn)義而不是使用真實(shí)的換行符。
數(shù)字
此外藤乙,在某些情況下猜揪,JSON 對(duì)數(shù)字的定義可能會(huì)有問題惭墓。JSON 規(guī)范中將數(shù)字定義成使用十進(jìn)制表示的任意精度有限浮點(diǎn)數(shù)坛梁。對(duì)于大多數(shù)應(yīng)用程序來說,這沒有問題腊凶。但是划咐,如果你需要使用十六進(jìn)制表示法或表示無窮大或 NaN 等值時(shí)拴念,那么 TOML 或 YAML 將能夠更好地處理它們。
{
"name": "example",
"description": "A really long description that needs multiple lines.\nThis is a sample project to illustrate why JSON is not a good configuration format. This description is pretty long, but it doesn't have any way to go onto multiple lines.",
"version": "0.0.1",
"main": "index.js",
"http://": "This is as close to a comment as you are going to get",
"keywords": ["example", "config"],
"scripts": {
"test": "./test.sh",
"do_stuff": "./do_stuff.sh"
},
"bugs": {
"url": "https://example.com/bugs"
},
"contributors": [{
"name": "John Doe",
"email": "johndoe@example.com"
}, {
"name": "Ivy Lane",
"url": "https://example.com/ivylane"
}],
"dependencies": {
"dep1": "^1.0.0",
"dep2": "3.40",
"dep3": "6.7"
}
}
JSON 的替代方案
選擇哪一種配置語言取決于你的應(yīng)用程序褐缠。每種語言都有各自的優(yōu)缺點(diǎn)政鼠,下面列出了一些可以考慮的選項(xiàng)。它們都是為配置而設(shè)計(jì)的語言队魏,每一種都比 JSON 這樣的數(shù)據(jù)語言更好公般。
name = "example"
description = """
A really long description that needs multiple lines.
This is a sample project to illustrate why JSON is not a \
good configuration format. This description is pretty long, \
but it doesn't have any way to go onto multiple lines."""
version = "0.0.1"
main = "index.js"
# This is a comment
keywords = ["example", "config"]
[bugs]
url = "https://example.com/bugs"
[scripts]
test = "./test.sh"
do_stuff = "./do_stuff.sh"
[[contributors]]
name = "John Doe"
email = "johndow@example.com"
[[contributors]]
name = "Ivy Lane"
url = "https://example.com/ivylane"
[dependencies]
dep1 = "^1.0.0"
# Why we depend on dep2
dep2 = "3.40"
dep3 = "6.7"
HJSON
HJSON 是一種基于 JSON 的格式,但具有更大的靈活性胡桨,可讀性也更強(qiáng)官帘。它支持注釋、多行字符串昧谊、不帶引號(hào)的鍵和字符串刽虹,以及可選的逗號(hào)。如果你想要 JSON 結(jié)構(gòu)的簡(jiǎn)單性呢诬,同時(shí)對(duì)配置文件更友好涌哲,那么可以考慮 HJSON。有一些可以將 HJSON 轉(zhuǎn)換為 JSON 的命令行工具尚镰,如果你使用的工具是基于 JSON 的阀圾,可以先用 HJSON 編寫配置,然后再轉(zhuǎn)換成 JSON狗唉。JSON5 是另一個(gè)與 HJSON 非常相似的配置語言稍刀。
{
name: example
description: '''
A really long description that needs multiple lines.
This is a sample project to illustrate why JSON is
not a good configuration format. This description
is pretty long, but it doesn't have any way to go
onto multiple lines.
'''
version: 0.0.1
main: index.js
# This is a a comment
keywords: ["example", "config"]
scripts: {
test: ./test.sh
do_stuff: ./do_stuff.sh
}
bugs: {
url: https://example.com/bugs
}
contributors: [{
name: John Doe
email: johndoe@example.com
} {
name: Ivy Lane
url: https://example.com/ivylane
}]
dependencies: {
dep1: ^1.0.0
# Why we have this dependency
dep2: "3.40"
dep3: "6.7"
}
}
HOCON
HOCON 是為 Play 框架設(shè)計(jì)的配置格式,在 Scala 項(xiàng)目中非常流行敞曹。它是 JSON 的超集账月,因此可以使用現(xiàn)有的 JSON 文件。除了注釋澳迫、可選逗號(hào)和多行字符串這些標(biāo)準(zhǔn)特性外局齿,HOCON 還支持從其他文件導(dǎo)入和引用其他值的鍵,避免重復(fù)代碼橄登,并使用以點(diǎn)作為分隔符的鍵來指定值的路徑抓歼,因此用戶可以不必將所有值直接放在花括號(hào)對(duì)象中。
name = example
description = """
A really long description that needs multiple lines.
This is a sample project to illustrate why JSON is
not a good configuration format. This description
is pretty long, but it doesn't have any way to go
onto multiple lines.
"""
version = 0.0.1
main = index.js
# This is a a comment
keywords = ["example", "config"]
scripts {
test = ./test.sh
do_stuff = ./do_stuff.sh
}
bugs.url = "https://example.com/bugs"
contributors = [
{
name = John Doe
email = johndoe@example.com
}
{
name = Ivy Lane
url = "https://example.com/ivylane"
}
]
dependencies {
dep1 = ^1.0.0
# Why we have this dependency
dep2 = "3.40"
dep3 = "6.7"
}
YAML
YAML(YAML 不是標(biāo)記語言)是一種非常靈活的格式拢锹,幾乎是 JSON 的超集谣妻,已經(jīng)被用在一些著名的項(xiàng)目中,如 Travis CI卒稳、Circle CI 和 AWS CloudFormation蹋半。YAML 的庫幾乎和 JSON 一樣無處不在。除了支持注釋充坑、換行符分隔减江、多行字符串染突、裸字符串和更靈活的類型系統(tǒng)之外,YAML 也支持引用文件辈灼,以避免重復(fù)代碼份企。
YAML 的主要缺點(diǎn)是規(guī)范非常復(fù)雜,不同的實(shí)現(xiàn)之間可能存在不一致的情況巡莹。它將縮進(jìn)視為嚴(yán)格語法的一部分(類似于 Python)司志,有些人喜歡,有些人不喜歡降宅。這會(huì)讓復(fù)制和粘貼變得很麻煩俐芯。
腳本語言
如果你的應(yīng)用程序是使用 Python 或 Ruby 等腳本語言開發(fā)的,并且你知道配置的來源是可靠的钉鸯,那么最好的選擇可能就是使用這些語言進(jìn)行配置吧史。如果你需要一個(gè)真正靈活的配置選項(xiàng),也可以在編譯語言中嵌入諸如 Lua 之類的腳本語言唠雕。這樣可以獲得腳本語言的靈活性贸营,而且比使用不同的配置語言更容易實(shí)現(xiàn)。使用腳本語言的缺點(diǎn)是它可能過于強(qiáng)大岩睁,當(dāng)然钞脂,如果配置來源是不受信任的,可能會(huì)引入嚴(yán)重的安全問題捕儒。
自定義配置格式
如果由于某種原因冰啃,鍵值配置格式不能滿足你的要求,并且由于性能或大小限制而無法使用腳本語言刘莹,那么可以考慮自定義配置格式阎毅。如果是這種情況,那么在做出選擇之前要想清楚点弯,因?yàn)槟悴粌H要編寫和維護(hù)一個(gè)解析器扇调,還要讓你的用戶熟悉另一種配置格式。
結(jié)論
有了這么多更好的配置語言抢肛,沒有理由還要使用 JSON狼钮。如果要?jiǎng)?chuàng)建需要用到配置的新應(yīng)用程序、框架或庫捡絮,請(qǐng)選擇 JSON 以外的其他選項(xiàng)熬芜。
英文原文:https://www.lucidchart.com/techblog/2018/07/16/why-json-isnt-a-good-configuration-language/