YAMLの落とし穴: ノルウェー問題とその他の静かな罠
YAMLの便利さは暗黙的な型推論から来ており、そこがまさに問題の起きる場所です。国コードがfalseになり、バージョン番号が桁を失い、時刻が整数になります。こうした罠とその回避方法を整理します。
YAMLは書きやすい形式です。波括弧も引用符もなく、インデントと値だけで 表現します。その便利さは一つの機能から来ています。引用符のない値の型を YAMLが推測してくれる点です。多くの場合、その推測は正しいです。しかし それ以外の場合には、データを静かに別のものへ変えてしまい、静かである ために本番環境で初めて気づくことになります。有名な例はブール値になって しまう国コードですが、これは知っておくべきより広いパターンの一例にすぎ ません。
暗黙的な型推論が根本原因です
YAMLは引用符のないスカラーに出会うと、それを解釈しようとします。123は
整数になり、trueはブール値になり、nullはnullになる、といった具合
です。型を要求していないのに、YAMLはテキストの形から型を推論します。
おかげでcount: 3を引用符なしで書けますが、同時に以下のすべての罠を
引き起こす原因でもあります。すべての罠に対する対処法は同じで、値に引用符
を付けることです。ただしいつ引用符を使うべきかを知っておく必要があり
ます。
ノルウェー問題
代表的な例は国コードのリストです。
countries:
- NO # Norway
- SE # Sweden
- FR # France
YAML 1.1仕様では、NOはブール値のfalseです。YES、ON、OFF、
TRUEとその大文字小文字のバリエーションも同様です。そのためノルウェーは
静かにfalseになり、パーサーはアプリケーションが文字列を期待している
ところにブール値を渡します。同じ論理で、化学物質リストのNO(一酸化窒素)
やユーザーのイニシャルもブール値になります。引用符を付けること、つまり
"NO"が対処法です。
YAML 1.2はブール値をtrue/falseだけに絞りましたが、多くのパーサーは
いまだに1.1の動作をデフォルトにしています。そのため安全だと決めつけ
られません。2文字の大文字の文字列は引用符が必要なものとして扱ってくだ
さい。
バージョン番号と60進法の罠
数値推論から生じるものがもう2つあります。
- **
version: 1.20**は浮動小数点の1.2としてパースされます。末尾の0が 消え、両者を区別したいときに1.20 == 1.2が真になります。バージョン 文字列は常に引用符を付けるべきです。version: "1.20"と書きます。 - **
time: 22:30**はYAML 1.1で60進法(sexagesimal)の整数として読まれる ことがあります。22*60 + 30 = 1350になります。時計の時刻として意図した フィールドが意味のない整数になります。引用符を付けてください。
先頭のゼロと8進数の落とし穴
id: 0755のような値は**8進数(octal)**として解釈され、10進数で493に
なることがあります。電話番号、郵便番号、ゼロ埋めのIDはすべて先頭のゼロを
失うか、別の解釈をされます。CSVと同様に、先頭のゼロが意味を持つ値は必ず
引用符を付けた文字列でなければなりません。
その他の鋭い角
- タブはインデントに使えません。 YAMLは空白を要求しており、紛れ込んだ タブはパースエラーになります。タブを挿入するエディタは分かりにくい失敗を 引き起こします。
- インデントが構造そのものです。 揃っていないキーは静かに誤った親の 子になったり、意図しない兄弟になったりします。有効なYAMLですが、誤った データです。
- アンカーとマージキー(
&、*、<<)は強力ですが、ドキュメントを 読みにくくし、パーサーによって動作が異なります。控えめに使ってください。
大半を防ぐルール
迷ったときは、別の型と間違われ得る文字列に引用符を付けてください。
意図せずブール値になる値(NO、YES、ON)、バージョン番号、時刻、
ゼロ埋めの数値が対象です。必要のない値に引用符を付けても失うものはなく、
上記のすべての罠を避けられます。大半が文字列と識別子からなる設定では、
手で調整した数値ファイルよりも引用符の規律が重要になります。
こうした問題をリリース前に捕まえるには、YAMLバリデーターが
ブラウザでドキュメントをパースし、構造とエラーを表示します。そのためNOが
文字列として通ったのかブール値として通ったのかを確認できます。YAMLが
あなたのケースに適した形式かどうかをまだ検討中なら、
JSONとYAMLの比較で、それぞれの危険な点を
それぞれが適する場面と照らし合わせて扱います。