YAML の細かい仕様の話

Pocket

YAML の細かい仕様の話

趣味アカウントのほうでバズった… ってほどでは無いけど、割と反響があったので、 YAML に関する話をいくつか。

YAML 1.2 では Boolean として扱うのは true, false だけ

https://yaml.org/type/bool.html

YAML 1.1 では 以下の文字が Boolean として認識されるのが一般的だ。
y|Y|yes|Yes|YES|n|N|no|No|NO|true|True|TRUE|false|False|FALSE|on|On|ON|off|Off|OFF

厳密には、 !!bool タグを使って Boolean 型を表現する場合の表記方法だが、基本的には多くのパーサーではタグを書かなくても Boolean 型として扱われる。

https://yaml.org/spec/1.2.0/#id2602744

しかし YAML 1.2 以降ではこれが、 true, false のみが Boolean として認識されるという、だいぶデカい破壊的変更が入っている。

ここで、 1.2.0 の主たる更新点を見てみると…
https://yaml.org/spec/1.2.2/ext/changes/#changes-in-version-12-revision-120-2009-07-21

Changes in version 1.2 (revision 1.2.0) (2009-07-21)

[...]

  • Only true and false strings are parsed as booleans (including True and TRUE); y, yes, on, and their negative counterparts are parsed as strings.
  • Underlines _ cannot be used within numerical values.
  • Octal values need a 0o prefix; e.g. 010 is now parsed with the value 10 rather than 8.
  • The binary and sexagesimal integer formats have been dropped.
  • The !!pairs, !!omap, !!set, !!timestamp and !!binary types have been dropped.
  • The merge << and value = special mapping keys have been removed.

参考日本語訳:

  • true と false の文字列のみがブーリアン(TrueとTRUEを含む)として解析される。 y, yes, on およびそれらの否定語は文字列として解析される。
  • アンダーライン _ は、数値の中では使用できません。
  • 8進数の値には0oのプレフィックスが必要です。例えば010は8ではなく10という値で解析されるようになりました。
  • 二進法と十進法の整数形式は廃止されました。
  • ペア、オマップ、セット、タイムスタンプ、バイナリ型は削除されました。
  • merge << と value = の特殊マッピングキーは削除されました。

うーん、現代の セマンティック バージョニング だったら、絶対にマイナー場ジョンアップじゃダメなヤツ。

こういった仕様のため、 yes, no と書いた際にどう扱われるかは、パーサーの実装による。

例えば、 PyYAML などは Boolean リテラルとして扱われるが、 go-yaml/yaml.v3 では文字列として扱われる。

内部パーサーの更新でこの解釈が変わってしまったために発生したのが、冒頭の問題だ。

Ansible などでは、真偽値を yes, no と書く慣習があるせいで、もうめちゃくちゃあっちこっちで yes, no って書いてるわ…

なお、冒頭の Docker Compose については、 Docker Compose version v2.17.3 で yes 等を書いてもエラーにせずワーニングだけ出すように修正された。
[BUG] Error "must be a boolean" with yes scalar in Docker Compose v2.17.0 · Issue #10465 · docker/compose


">" は全ての改行をスペースに置き換えるわけではない

ブロックスタイルの文字列表記で、 ">" インジゲータを置くと、次の行から途中の改行が半角スペースに置き換えられると説明されがちだ。

foo: >
  trimmed
  line

しかし、半角スペースに置き換えられる条件はもう少し複雑だ。

https://yaml.org/spec/1.2.2/#line-folding

この Line Folding と言うのがこれに当たるのだが、これは長い行を折りたたんで読みやすくするための機能だ。

折りたたまれた部分(改行)が半角スペース (0x20) に変換されるものだが、

  • 複数の改行が続く場合は最初の改行が破棄され、残りは保持される
  • 先頭の空白を含むテキスト行(つまりインデントされた行)は、行の折り返しは適用されない。

という仕様がある。

このため、以下のように動作する。

YAML 入力:

foo: >
  trimmed
  line
  breaking

  but blank line and
    nested
    line
  are are retained.

JSON 出力:

{ "foo": "trimmed line breaking\nbut blank line and\n  nested\n  line\nare are retained.\n" }

つまり、「改行を消す」というよりは、「次の行のインデントの高さがブロックの先頭と同じ場合のみ、行の折りたたみと見なして改行を削除する」という動作となる。


マージキー (<<) によるマージは 1回 しか記述できない。

複数のマップをマージしたい場合は、 シーケンス(配列)によって指定する。

https://yaml.org/type/merge.html

YAML 入力:

-
  - &ANCHER1 { '0': 0, a: 0, b: 0 }
  - &ANCHER2 { b: 99, c: 99 }
  - &ANCHER3 { c: 255, d: 255 }
#- # Eror
#  '0': 1
#  << : *ANCHER1
#  << : *ANCHER2
#  << : *ANCHER3
#  a: 1
- # Single map
  b: 1
  << : *ANCHER2
- # Override multiple maps
  '0': 1
  << : 
    - *ANCHER1
    - *ANCHER2
    - *ANCHER3
  a: 1

JSON 出力:

[
  [
    { "a": 0, "b": 0 },
    { "b": 99, "c": 99 },
    { "c": 255, "d": 255 }
  ],
  { "b": 1, "c": 99 },
  { "0": 1, "a": 1, "b": 0, "c": 99, "d": 255 }
]

シーケンスで複数のマップを指定した場合で、キーが重複している物は、シーケンス内では順序が前のものが常に優先される。
一方で、マージキーで指定したモノよりは、マージ先のものが優先となる。

ただ、パーサーによっては マージキー (<<) を複数書く誤った書き方に対応しているモノがあり、
実際、 Docker Compose v2.17.0 で、内部パーサーが go-yaml/v3 に切り替わったときに、誤った書き方が対応から未対応に変わって、以下のようなエラーが出てハマった。

mapping key "<<" already defined at line nnn

[BUG]using multiple yaml alias not working. mapping key "<<" already defined · Issue #10411 · docker/compose · GitHub

もちろんこれは、マージキーを複数書く方が悪いので、 YAML の方を直す必要がある。

そして、もっとも恐ろしいことは、この機能も YAML 1.2 で削除されていることだ。

さすがに、主要な YAML 1.2 対応パーサーでも、マージ機能の削除まで行ったものは少ないが…

コメントを残す

メールアドレスが公開されることはありません。

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください