人間が書くための設定ファイルに何を選ぶのか

# これは何？

ソフトウェアを新たに起こしていて。
設定を変えられるようにする必要があり。
設定の GUI とか作るのだるいので設定ファイルをユーザーに書いてもらおうと思い。

設定ファイルの書式として何を選ぶのがいいのかなと思って調べたので書いてみた。

# 登場人物

候補に上がったものと、そういう設定ファイルの形式あるよねと思ったけどまったく採用する気がなかったものを列挙すると。

|名前と Wikipedia 等へのリンク|ひとこと|
|:--|:--|
|[INI]( https://ja.wikipedia.org/wiki/INI%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB )|無理|
|[XML](https://ja.wikipedia.org/wiki/Extensible_Markup_Language)|人類向きじゃない|
|[YAML](https://ja.wikipedia.org/wiki/YAML)|複雑|
|[JSON](https://ja.wikipedia.org/wiki/JavaScript_Object_Notation)|コメント書けない。ケツカンマ問題がある|
|[JSONC](https://github.com/microsoft/node-jsonc-parser)|有力候補|
|[JSON5](https://json5.org/)|有力候補|
|[TOML](https://ja.wikipedia.org/wiki/TOML)|1.0.0 がリリースされたばかり|

## ケツカンマ問題とは

たとえば JSON で

```json:JSON
[1,2,3,4]
```
なら末尾のカンマが無いのが自然だし、末尾にカンマを書きたいモチベーションもほとんどない。しかし

```json:JSON
[
  { "name": "An object that takes many lines to describe" },
  { "name": "Many lines of definition" },
  { "name": "Multi-line object" }
]
```

のような場合、末尾の要素を末尾以外に移動したり、末尾に要素を追加したりすると「ここにカンマがあると文法エラーです」とか「ここにはカンマが必要です」とかいう文法エラーに見舞われる。というわけで、順序変更や末尾付近で追加削除するときにストレスを感じる。

これがケツカンマ問題である。

ケツカンマ問題が問題なのは、行末にコンマが必要な場所と行末のコンマが禁止の場所が両方あるからなので

* 全部コンマありなら問題ないような文法
* 全部コンマなしなら問題ないような文法

のいずれかなら人間としては楽になる。
具体的には、

* 最後の要素の後にコンマがあっても許すので、全部コンマありにすれば良い（JSON5 など)
* 行末にコンマ不要なので、全部コンマなしにすれば良い（TOML など）

のような対応がある。

# ひとつひとつ説明

## INI

なんとなく人間が書くのに便利に見える感じだけど、仕様が不明瞭だし、文字コードもどうすればいいのかよくわからないのでお勧めできない。
歴史的経緯以外に使うべき理由はまったくないと思う。

少なくとも Windows 3.1 の頃から使われている。
Win32 API で読み書きできる。

Win32 API で書いた INI ファイルを Win32 API で読むなら問題ないんだけど、手で書いた INI ファイルを Win32 API で読むとなると問題があり。
私の記憶が確かなら、 Win9x 系 と WinNT 系で、同じファイルを読んだ結果が異なる場合があったと思う。等号の前後に空白がある場合の動作だったかなぁ。よくおぼえてない。

キー・バリューペアは「セクション」と呼ばれるブロックでグループ化されるんだけど、セクションのネストができないので割と困る。

Python に [`configparser`]( https://docs.python.org/3/library/configparser.html ) というライブラリがあってINI形式にということになっているけど、Windows の INI ファイルと似ているだけで違う形式。ややこしい。
[`configparser`]( https://docs.python.org/3/library/configparser.html ) の INI も、軽く探した範囲内では文法の仕様が見つけられなかった。

## XML

人間が書くのはつらそうな見た目だけど、仕様も明瞭で文字コードもどうすればいいのか明らかという INI とは対照的な感じ。

書きやすさということでは、閉じタグを書くのがだるすぎる。

値に型はなく、 `1` が数なのか文字列なのかはアプリケーションが決める。

それと。

```xml
<foo hoge="1"></foo>
```

と

```xml
<foo><hoge>1</hoge></foo>
```

のどっちにするのがいいのかわからないという問題があり、めんどくさい。

さらに。

```xml
<foo>
  <hoge>
    1
  </hoge>
</foo>
```

のようにしたときに「`1`」の前後やタグの前後にある空白改行をどうするかという問題もある。これは XML の仕様ではなく、読み取るアプリケーションの仕様で決めることになる。

各 XML がアプリケーションにとって妥当かどうかを確認するための仕組みがあるのは便利だと思う。その仕組のための XML を書くのが面倒だけど。

## YAML

私自身はほぼ使ったことがないんだけど

* 人間が書くためにデザインされている
* 値に型がある
* 設定ファイルを書くために必要な機能はだいたい揃っている

ということで、大いに期待していた。

循環参照みたいなものを表現できるのは、ここで挙げているものの中では YAML だけというのは重要な特徴だと思う。

```ruby:ruby
o={a:"hoge"}
o[:b]=o
x={c:[o,"fuga"]}
#=> {c: [{a: "hoge", b: {...}}, "fuga"]}
irb(main):018> puts o.to_yaml
puts x.to_yaml
```

とすると、

```yaml:yaml
---
:c:
- &1
  :a: hoge
  :b: *1
- fuga
```

となり、ちゃんと循環参照ができる。できると困る場合もあるので良し悪しではあるけれど。

文字コードについての明確なルールがあるのかどうかはよく知らないが、utf-8 で書くのが一般的という暗黙の了解がありそう。

しかし。
文法がわりとリッチで、そんなに機能はいらないよ、という気分になる。

それと。
文法にバージョンがあるのが地味に罠になるらしい。

### ノルウェー問題

[ノルウェー問題](https://www.google.com/search?q=yaml+norway+problem) 

```ruby:ruby
YAML.load( "{Japan: {lang: ja}, Norway: {lang: no}}" )
# => {"Japan"=>{"lang"=>"ja"}, "Norway"=>{"lang"=>false}}
```

と呼ばれる問題の存在に気づいてからは、だいぶ使いたい気持ちがなくなった。

## JSON

よく使う。色々なツール・ライブラリが対応していて安心感がある。
文法も簡単。必須と思われる機能はほぼ揃っている。
この記事を書くときまで知らなかったんだけど、文字コードも BOMなしUTF-8 必須と決まっているらしい。

多くのパーサが文法エラーの JSON を読んだときに「エラーです」と言ってくれるので安心感があるんだけど、浮動小数点数の非数などを JSON にすると不正なJSONをつくるライブラリがわりとたくさんあるので要注意 (詳しくは、拙記事[非数をJSONに入れようとするとどうなるか]( https://qiita.com/Nabetani/items/1e9af1ee1d25e3b463a0 ) を参照)。

RFC もある。素晴らしい。

しかし。

* コメントを書く方法がない。
* ケツカンマ問題がつらい。
* 16進数が書けない。
* 浮動小数点数の非数と無限を入れる方法がない。

という欠点がある。特にコメントが書けないのが痛い。

## JSONC

よく使う。

JSON の「コメントが書けない」という欠点を補ったもの。
Visual Studio Code の設定ファイルで使われている。

便利なのでわりと使うけど、仕様はいまいちはっきりしない。
文字コードの規定もはっきりしない。
ケツカンマ問題は改善されていないし、16進数も書けない。

## JSON5

JSON の欠点を解消したもの。
仕様も明瞭。すばらしい。

コメントも書けるし、ケツカンマ問題も解消されている。
浮動小数点数の非数の無限も入れられる。
16進数も複数行に渡る文字列も書ける。

それでいて必要を超えないシンプルな仕様。

文字コードに関する規定がないように思うのがほんの少し残念だけど、まあいいとおもう。

しかしなぜかあまり広まっている気がしない。

## TOML

期待のホープ。と書いていたが、割と広まっている気がする。

仕様は明瞭。日本語訳もある。
コメントも書ける。
値に型もある。日付型をサポートしているのが地味に便利。
浮動小数点数の非数も無限も入れられる。
ケツカンマ問題も完全に解消されている。
16進数・8進数・2進数も書ける。

文字コードの規定も明瞭。

とはいえ欠点もある。

JSON なら
```json:json
{"a":[{"b":1,"c":[{"d":1},{"d":2}]},{"e":{"f":3}},"hoge"]}
```

となるようなものを TOML で書くのが難しい。[^1]

ruby の TomlRB で dump しようとするとうまくできないぐらいには難しい。

```ruby:ruby
require "toml-rb"
#=> true
TomlRB.dump({a:[{b:1, c:[{d:1}, {d:2}]}, {e:{f:3}}, "hoge"]})
#=> 略 'TomlRB::Dumper#sort_pairs': 略 (NoMethodError)
```

実際のところ、

```TOML:TOML
a = [
  { b = 1, c = [
    { d = 1 },
    { d = 2 },
  ] },
  { e = { f = 3 } },
  "hoge",
]
```

と書けるんだけど、書けるとしても、どうやって書けばいいのか悩ませてしまうのはよくない。

ちなみに最後の `"hoge"` が無ければ

```TOML:TOML
[[a]]
  b = 1
[[a.c]]
  d = 1
[[a.c]]
  d = 2
[[a]]
[a.e]
  f = 3
```

と書けるんだけど、最後の `"hoge"` のせいで `[[a]]` のような記法が使えなくなり、`a = [略]` というTOML っぽくない書き方になる。

設定ファイルということだとこういう複雑さにはなりにくいかもしれいし、そういう場面は TOML 向きではないということなんだろうけれど、困ることはある。

## Jesth

https://github.com/pyrustic/jesth

を拾い読みした。使ったことはない。

TOML の、ネストが深くなると、ネストが浅いときに使える文法が使えなくなるという気持ち悪さを解消。
YAML より曖昧さが少なく、文法がシンプル。
JSON より表現力が豊か。

という感じ。

ドキュメントを見る限り

* Python が念頭にある
* 非数・無限への対応はない
* 3.14 は `float` で、3.14e0 は `decimal.Decimal` という意外な仕様
* 全体的な文法はシンプルでわかりやすく読みやすい
* Python と同様、インデントで表現するので好き嫌いはありそう
* JSON に無い型としては、複素数、日付ありの時刻、日付なしの時刻、バイト列（base64)、などがある

非数無限がないのと、3.14e0 が `decimal.Decimal` になるのが気持ち悪いので使わないかなと思った。

# まとめ

私見を中心にまとめた。
「書きやすい」とか「有名？」は完全に私見。
「RFCがある」の欄は、無いことを確認するのは難しいのであまり自信がない。

下表の I, X, Y, J, JC, J5 T は、順に INI、XML、YAML、JSON、JSONC、JSON5、TOML。

|特徴|I|X|Y|J|JC|J5|T|
|:--|:--:|:--:|:--:|:--:|:--:|:--:|:--:|
|人間が書きやすい|良|悪|良|普通|やや良|良|良|
|コメント書ける|✅|✅|✅|❌|✅|✅|✅|
|階層化|❌|✅|✅|✅|✅|✅|✅|
|値に型がある|❌|❌|✅|✅|✅|✅|✅|
|非数と無限|n/a|n/a|✅|❌|❌|✅|✅|
|16進数|n/a|n/a|✅|❌|❌|✅|✅|
|文字コードをどうすればいいか明瞭|❌|✅|❓|✅|❓|❓|✅|
|仕様が明瞭|❌|✅|✅|✅|❓|✅|✅|
|RFCがある|❌|✅|❌|✅|❌|❌|❌|
|仕様が簡単? 複雑?|n/a|複雑|複雑|簡単|n/a|普通|普通|
|有名？|普通|有名|やや有名|有名|やや有名|普通|普通|

機械生成が視野に入っている場合と、深いネストになるかもしれない場合は JSON5 が良さそう。
JavaScript や JSON に慣れている人にはわかりやすいというメリットもある。

機械生成しないし、深いネストの可能性もほぼないということなら、TOML が良さそう。
設定ファイルをテキストにする、ということに全然慣れてない人なら TOML の方が読みやすいかもしれない。

と思う。

といわけで、 JSON5 と TOML がんばれ。

[^1]: 以前 `{"a":[1,[2,{"b":1}]]}` と書いていたが、[別の記事のコメント](https://qiita.com/Leapcell/items/0faa93867be4bbe49bb7#comment-a09023a4881de223318e) で簡単だと教えていただいた（ありがとうございます）ので、例を変えた。