译者|无明
编辑|覃云
很多项目利用 JSON 作为配置文件,最明显的例子便是 npm 和 yarn 利用的 package.json 文件。当然,还有很多其他文件,例如 CloudFormation(最初只有 JSON,但现在也支持 YAML)和 composer(PHP)。

但是,JSON 实际上是一种非常糟糕的配置措辞。别误会我的意思,我实在是喜好 JSON 的。它是一种相对灵巧的文本格式,对付机器和人类来说都很随意马虎阅读,而且是一种非常好的数据交流和存储格式。但作为一种配置措辞,它有它的不敷。
为何盛行用 JSON 作配置措辞?
将 JSON 用作配置文件有几个方面的缘故原由,个中最大的缘故原由可能是它很随意马虎实现。很多编程措辞的标准库都支持 JSON,开拓职员或用户可能已经很熟习 JSON,以是不须要学习新的配置格式就可以利用那些产品。现在险些所有的工具都供应 JSON 支持,包括语法突出显示、自动格式化、验证工具等。
这些都是很好的情由,但这种无处不在的格式实在不适宜用作配置。
JSON 的问题
缺少注释
注释对付配置措辞而言绝对是一个主要的功能。注释可用于标注不同的配置选项、阐明为什么要配置成特定的值,更主要的是,在利用不同的配置进行测试和调试时须要临时注释掉部分配置。当然,如果只是把 JSON 当作是一种数据交流格式,那么就不须要用到注释。
我们可以通过一些方法给 JSON 添加注释。一种常见的方法是在工具中利用分外的键作为注释,例如“//”或“__comment”。但是,这种语法的可读性不高,并且为了在单个工具中包含多个注释,须要为每个注释利用唯一的键。David Crockford(JSON 的发明者)建议利用预处理器来删除注释。如果你的运用程序须要利用 JSON 作为配置,那么完备没问题,不过这确实带来了一些额外的事情量。
一些 JSON 库许可将注释作为输入。例如,Ruby 的 JSON 模块和启用了 JsonParser.Feature.ALLOW_COMMENTS 功能的 Java Jackson 库可以处理 JavaScript 风格的注释。但是,这不是标准的办法,而且很多编辑器无法精确处理 JSON 文件中的注释,这让编辑它们变得更加困难。
过于严格
JSON 规范非常严格,这也是为什么实现 JSON 解析器会这么大略,但在我看来,它还会影响可读性,并且在较小程度上会影响可写性。
低信噪比
与其他配置措辞比较,JSON 显得非常喧华。JSON 的很多标点符号对可读性毫无帮助,况且,工具中的键险些都是标识符,以是键的引号实在是多余的。
此外,JSON 须要利用花括号将全体文档包围起来,以是 JSON 是 JavaScript 的子集,并在流中发送多个工具时用于界定不同的工具。但是,对付配置文件来说,最表面的大括号实在没有任何用途。在配置文件中,键值对之间的逗号也是没有必要的。常日情形下,每行只有一个键值对,以是利用换行作为分隔符更故意义。
说到逗号,JSON 居然不许可在结尾涌现逗号。如果你须要在每个键值对之后利用逗号,那么至少该当接管结尾的逗号,由于有了却尾的逗号,在添加新条款时会更随意马虎,而且在进行 commit diff 时也更清晰。
长字符串
JSON 作为配置格式的另一个问题是,它不支持多行字符串。如果你想在字符串中换行,必须利用“\n”进行转义,更糟糕的是,如果你想要一个字符串在文件中另起一行显示,那就彻底没办法了。如果你的配置项里没有很长的字符串,那就不是问题。但是,如果你的配置项里包括了长字符串,例如项目描述或 GPG 密钥,你可能不肯望只是利用“\n”来转义而不是利用真实的换行符。
数字
此外,在某些情形下,JSON 对数字的定义可能会有问题。JSON 规范中将数字定义成利用十进制表示的任意精度有限浮点数。对付大多数运用程序来说,这没有问题。但是,如果你须要利用十六进制表示法或表示无穷大或 NaN 等值时,那么 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\公众, \"大众//\"大众: \"大众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 的替代方案
选择哪一种配置措辞取决于你的运用程序。每种措辞都有各自的优缺陷,下面列出了一些可以考虑的选项。它们都是为配置而设计的措辞,每一种都比 JSON 这样的数听说话更好。
TOML
TOML 是一种越来越盛行的配置措辞。Cargo(Rust 的构建工具)、pip(Python 包管理器)和 dep(Go 措辞依赖管理器)都在利用 TOML。TOML 有点类似于 INI 格式,但与 INI 不同的是,它有一个标准规范,并且嵌套构造有明确定义的语法。它比 YAML 大略得多,如果你的配置很大略,那么 TOML 就非常得当。但是,如果你的配置具有大量的嵌套构造,那么利用 TOML 可能就有点冗长,而另一种格式(如 YAML 或 HOCON)可能是更好的选择。
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 的格式,但具有更大的灵巧性,可读性也更强。它支持注释、多行字符串、不带引号的键和字符串,以及可选的逗号。如果你想要 JSON 构造的大略性,同时对配置文件更友好,那么可以考虑 HJSON。有一些可以将 HJSON 转换为 JSON 的命令行工具,如果你利用的工具是基于 JSON 的,可以先用 HJSON 编写配置,然后再转换成 JSON。JSON5 是另一个与 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 框架设计的配置格式,在 Scala 项目中非常盛行。它是 JSON 的超集,因此可以利用现有的 JSON 文件。除了注释、可选逗号和多行字符串这些标准特性外,HOCON 还支持从其他文件导入和引用其他值的键,避免重复代码,并利用以点作为分隔符的键来指定值的路径,因此用户可以不必将所有值直接放在花括号工具中。
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 不是标记措辞)是一种非常灵巧的格式,险些是 JSON 的超集,已经被用在一些著名的项目中,如 Travis CI、Circle CI 和 AWS CloudFormation。YAML 的库险些和 JSON 一样无处不在。除了支持注释、换行符分隔、多行字符串、裸字符串和更灵巧的类型系统之外,YAML 也支持引用文件,以避免重复代码。
YAML 的紧张缺陷是规范非常繁芜,不同的实现之间可能存在不一致的情形。它将缩进视为严格语法的一部分(类似于 Python),有些人喜好,有些人不喜好。这会让复制和粘贴变得很麻烦。
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 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/ivylange\"大众 dependencies: dep1: ^1.0.0 # Why we depend on dep2 dep2: \"大众3.40\"大众 dep3: \"大众6.7\"大众
脚本措辞
如果你的运用程序是利用 Python 或 Ruby 等脚本措辞开拓的,并且你知道配置的来源是可靠的,那么最好的选择可能便是利用这些措辞进行配置。如果你须要一个真正灵巧的配置选项,也可以在编译措辞中嵌入诸如 Lua 之类的脚本措辞。这样可以得到脚本措辞的灵巧性,而且比利用不同的配置措辞更随意马虎实现。利用脚本措辞的缺陷是它可能过于强大,当然,如果配置来源是不受信赖的,可能会引入严重的安全问题。
自定义配置格式
如果由于某种缘故原由,键值配置格式不能知足你的哀求,并且由于性能或大小限定而无法利用脚本措辞,那么可以考虑自定义配置格式。如果是这种情形,那么在做出选择之前要想清楚,由于你不仅要编写和掩护一个解析器,还要让你的用户熟习另一种配置格式。
结 论
有了这么多更好的配置措辞,没有情由还要利用 JSON。如果要创建须要用到配置的新运用程序、框架或库,请选择 JSON 以外的其他选项。
英文原文
https://www.lucidchart.com/techblog/2018/07/16/why-json-isnt-a-good-configuration-language/