學(xué)習(xí)完整課程請移步 互聯(lián)網(wǎng) Java 全棧工程師
很多項(xiàng)目使用 JSON 作為配置文件,最明顯的例子就是 npm 和 yarn 使用的 package.json 文件。當(dāng)然,還有很多其他文件,例如 CloudFormation(最初只有 JSON,但現(xiàn)在也支持 YAML)和 composer(PHP)。
但是,JSON 實(shí)際上是一種非常糟糕的配置語言。別誤會我的意思,我其實(shí)是喜歡 JSON 的。它是一種相對靈活的文本格式,對于機(jī)器和人類來說都很容易閱讀,而且是一種非常好的數(shù)據(jù)交換和存儲格式。但作為一種配置語言,它有它的不足。
為什么流行使用 JSON 作為配置語言?
將 JSON 用作配置文件有幾個(gè)方面的原因,其中最大的原因可能是它很容易實(shí)現(xiàn)。很多編程語言的標(biāo)準(zhǔn)庫都支持 JSON,開發(fā)人員或用戶可能已經(jīng)很熟悉 JSON,所以不需要學(xué)習(xí)新的配置格式就可以使用那些產(chǎn)品?,F(xiàn)在幾乎所有的工具都提供 JSON 支持,包括語法突出顯示、自動(dòng)格式化、驗(yàn)證工具等。
這些都是很好的理由,但這種無處不在的格式其實(shí)不適合用作配置。
JSON 的問題
缺乏注釋
注釋對于配置語言而言絕對是一個(gè)重要的功能。注釋可用于標(biāo)注不同的配置選項(xiàng)、解釋為什么要配置成特定的值,更重要的是,在使用不同的配置進(jìn)行測試和調(diào)試時(shí)需要臨時(shí)注釋掉部分配置。當(dāng)然,如果只是把 JSON 當(dāng)作是一種數(shù)據(jù)交換格式,那么就不需要用到注釋。
我們可以通過一些方法給 JSON 添加注釋。一種常見的方法是在對象中使用特殊的鍵作為注釋,例如“//”或“__comment”。但是,這種語法的可讀性不高,并且為了在單個(gè)對象中包含多個(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 解析器會這么簡單,但在我看來,它還會影響可讀性,并且在較小程度上會影響可寫性。
低信噪比
與其他配置語言相比,JSON 顯得非常嘈雜。JSON 的很多標(biāo)點(diǎn)符號對可讀性毫無幫助,況且,對象中的鍵幾乎都是標(biāo)識符,所以鍵的引號其實(shí)是多余的。
此外,JSON 需要使用花括號將整個(gè)文檔包圍起來,所以 JSON 是 JavaScript 的子集,并在流中發(fā)送多個(gè)對象時(shí)用于界定不同的對象。但是,對于配置文件來說,最外面的大括號其實(shí)沒有任何用處。在配置文件中,鍵值對之間的逗號也是沒有必要的。通常情況下,每行只有一個(gè)鍵值對,所以使用換行作為分隔符更有意義。
說到逗號,JSON 居然不允許在結(jié)尾出現(xiàn)逗號。如果你需要在每個(gè)鍵值對之后使用逗號,那么至少應(yīng)該接受結(jié)尾的逗號,因?yàn)橛辛私Y(jié)尾的逗號,在添加新條目時(shí)會更容易,而且在進(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 對數(shù)字的定義可能會有問題。JSON 規(guī)范中將數(shù)字定義成使用十進(jìn)制表示的任意精度有限浮點(diǎn)數(shù)。對于大多數(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)。它支持注釋、多行字符串、不帶引號的鍵和字符串,以及可選的逗號。如果你想要 JSON 結(jié)構(gòu)的簡單性,同時(shí)對配置文件更友好,那么可以考慮 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 文件。除了注釋、可選逗號和多行字符串這些標(biāo)準(zhǔn)特性外,HOCON 還支持從其他文件導(dǎo)入和引用其他值的鍵,避免重復(fù)代碼,并使用以點(diǎn)作為分隔符的鍵來指定值的路徑,因此用戶可以不必將所有值直接放在花括號對象中。
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),有些人喜歡,有些人不喜歡。這會讓復(fù)制和粘貼變得很麻煩。
腳本語言
如果你的應(yīng)用程序是使用 Python 或 Ruby 等腳本語言開發(fā)的,并且你知道配置的來源是可靠的,那么最好的選擇可能就是使用這些語言進(jìn)行配置。如果你需要一個(gè)真正靈活的配置選項(xiàng),也可以在編譯語言中嵌入諸如 Lua 之類的腳本語言。這樣可以獲得腳本語言的靈活性,而且比使用不同的配置語言更容易實(shí)現(xiàn)。使用腳本語言的缺點(diǎn)是它可能過于強(qiáng)大,當(dāng)然,如果配置來源是不受信任的,可能會引入嚴(yán)重的安全問題。
自定義配置格式
如果由于某種原因,鍵值配置格式不能滿足你的要求,并且由于性能或大小限制而無法使用腳本語言,那么可以考慮自定義配置格式。如果是這種情況,那么在做出選擇之前要想清楚,因?yàn)槟悴粌H要編寫和維護(hù)一個(gè)解析器,還要讓你的用戶熟悉另一種配置格式。
結(jié)論
有了這么多更好的配置語言,沒有理由還要使用 JSON。如果要?jiǎng)?chuàng)建需要用到配置的新應(yīng)用程序、框架或庫,請選擇 JSON 以外的其他選項(xiàng)。
英文原文:https://www.lucidchart.com/techblog/2018/07/16/why-json-isnt-a-good-configuration-language/