🦟

y座標のキーだけ "y" になる不可解な挙動とYAML仕様

2023/11/10に公開

環境

RUBY_VERSION   # => "3.2.2"
require "yaml"
YAML           # => Psych
YAML::VERSION  # => "5.1.1"

事の発端

YAML.dump("x" => 1, "y" => 2, "z" => 3)  # => "---\nx: 1\n\"y\": 2\nz: 3\n"

なぜ y だけダブルクォーテーションで囲まれた "y" なのだろう?

コードを読む

https://github.com/ruby/psych/blob/51cc86ff3f7cf98bc48d7eb1adf1e3c34b4ac475/lib/psych/visitors/yaml_tree.rb#L275-L276

なんか特別扱いされてた。n もいるし。y はまさかの yes (true) として認識されかけていたようだ。つまり y を y と書いてしまうと、YAML側からしたら真偽値の真と区別がつかなくなってしまうため "y" としたらしい。

ということはもし YAML で "y": "y" と書くべきところを y: y と書いてしまうと true: true と解釈されてしまうということだ。

実際に確認してみると、

YAML.load("---\ny: y\n")  # => {"y"=>"y"}

そうならんのんかい!

じゃあ y を "y" にした意図はなんだったんだろうか。

バージョンの確認

YAML.dump("", header: true)  # => "%YAML 1.1\n--- ''\n"

1.1 とのこと。

常識的に考えたら YAML_SPEC_VERSION のような定数を用意してくれていてもよさそうだが、コード内にはマジックナンバー 1.1 が散乱していたので、こんな方法で確認するしかなかった。

なお、

Psych::LIBYAML_VERSION  # => "0.2.5"

というのもあったがこれじゃない。

1.1 と 1.2 の違い

項目 YAML 1.1 YAML 1.2
発行年 2005 2009
JSONとの互換性 なし あり
文字列の扱い 矛盾する場合が多く、解析が難しい 明確であり、解析が容易
データ構造 一部のデータ構造が複雑 より単純で直感的
空白の扱い 曖昧 より厳格

ようするに 1.1 はめちゃくちゃらしい。

1.2 仕様の YAML ライブラリはない?

探したけど Ruby には見つからなかった。ChatGPT に聞くと Psych を紹介してくるけど、それは違う。

まとめ

y は、まさかの true のことで、y をそのまま書き出してしまうと、次に読み込んだとき true になってしまうので "y" としたのだろうと考えたが、実際には、 y は true ではなく "y" として読み取られたので、結局何がなんだかわけがわからない。

XML の代替として期待されて登場した YAML だが 1.1 の仕様はめちゃくちゃだった。そこで 1.2 の仕様ができたけど残念ながら Ruby の標準ライブラリはいまだに 1.1 仕様のままだった。

参照

https://yaml.org/
https://yaml.org/type/bool.html
https://sue445.hatenablog.com/entry/2021/01/16/003844
https://www.wantedly.com/companies/wantedly/post_articles/540711
https://shinkufencer.hateblo.jp/entry/2018/06/10/230945

Discussion