背景
自分のツールサイトに、YAML と JSON を相互に変換するページを置いています。
https://hashitosystem.com/tools/yamljson/
中では js-yaml 4.1.0 というライブラリで YAML を読んでいます。
このツールで JSON にした値が、Python で同じ YAML を読んだときと同じになるのか、実は確かめたことがありませんでした。
YAML の型の推測は前から苦手で(いつも引用符を付けるか迷います…)、一度ちゃんと並べて比べてみることにしました。
前提
YAML は、設定ファイルによく使われるデータの書き方だ。
GitHub Actions のワークフローや Docker Compose の設定で見かけると思います。
JSON と違って、値を引用符で囲まなくても書ける。
そのかわり、囲まなかった値が文字列なのか数値なのか真偽値なのかは、パーサー(YAML を読み込むライブラリ)が推測します。
この推測の規則が、YAML の版で違う。
YAML 1.1(2005年)は yes / no / on / off を真偽値として読み、0 で始まる数を8進数、コロンで区切った数を60進数として読む。
YAML 1.2(2009年)はこれをやめて、真偽値は true / false だけ、という具合に絞った。
仕様はここです。
https://yaml.org/spec/1.2.2/
同じ11行を読ませる
比べたのは、下記の11行です。
国コードやバージョン番号、郵便番号、時刻、短いコミットハッシュのような「よくありそうな値」を並べました。
country: no
enabled: yes
light: on
version: 1.10
zip: 012345
octal: 0o17
time: 22:30
id: 1e3
tilde: ~
date: 2026-10-21
sha: 0123e4
js-yaml は、ツールのページを開いた状態でブラウザのコンソールから jsyaml.load を呼びました。
jsyaml はページが CDN(ライブラリを配るサーバー網)から読み込んでいる js-yaml 4.1.0 のグローバル変数で、ツールの変換ボタンも中で同じ関数を呼んでいます。
y は上の11行を改行でつないだ文字列です。
JSON.stringify(jsyaml.load(y));
PyYAML は 6.0.3 を使い、上の11行を t.yaml というファイルに保存して下記で読みました。
safe_load は PyYAML の標準的な読み込み関数で、default=str は日付の型を JSON にできないので文字列にするための指定です。
import yaml, json
print(json.dumps(yaml.safe_load(open('t.yaml')), default=str))
結果
下記が、それぞれの出力を1行ずつに並べ直したものです(左が js-yaml、右が PyYAML)。
country : "no" | false
enabled : "yes" | true
light : "on" | true
version : 1.1 | 1.1
zip : 12345 | 5349
octal : 15 | "0o17"
time : "22:30" | 1350
id : 1000 | "1e3"
tilde : null | null
date : "2026-10-21T00:00:00.000Z" | "2026-10-21"
sha : 1230000 | "0123e4"
値が一致したのは version と tilde の2行だけでした。
date はどちらも日付として読んでいますが、JSON にした形が違います。
いくつか補足します。
- no / yes / on は、PyYAML では真偽値になる。国コードの no(ノルウェー)が false になる、いわゆる「ノルウェー問題」です
- 012345 は、js-yaml では先頭の0を落とした10進数、PyYAML では8進数として読まれて 5349 になった。郵便番号や社員番号が別の数になる
- 22:30 は、PyYAML では 22×60+30 の 1350 になった。60進数の規則です
- 1e3 は、js-yaml では指数表記の 1000。PyYAML は小数点の無い指数表記を数として読まないので文字列のまま
- 0123e4 は、短いコミットハッシュのつもりでも、js-yaml では 0123×10の4乗 = 1230000 になった
- 1.10 はどちらも 1.1 で、バージョン番号の末尾の0が消えた
どちらが正しい、という話ではない気がします。
js-yaml は 1.2 の規則に寄せていて、PyYAML は 1.1 の規則のまま、というだけです。
ただ、同じファイルを両方で読むと、黙って別の値になる。
JSON から YAML にするときは引用符が付く
逆向きも確かめました。
ツールの「JSON → YAML」は、中で jsyaml.dump を呼んでいます。
文字列の "no" や "012345" を渡すと、下記のように出ました。
country: 'no'
version: '1.10'
zip: '012345'
time: '22:30'
js-yaml は、1.1 の規則で読むパーサーが別の型にしてしまう文字列には、自分で引用符を付けている。
noCompatMode: true を渡すと country: no と引用符なしで出るので、既定で「1.1 のパーサーでも壊れない書き方」を選んでいることになります。
この4行を PyYAML で読み直すと、4つとも文字列のまま('no' や '012345')でした。
壊れやすいのは、人が手で書いた YAML を読むほうです。
なぜ引用符を付けるのか
設定ファイルは、書いた人と読むプログラムが別なことが多いです。
Node.js のツールで確かめた YAML を、CI(自動テストやデプロイを回す仕組み)では Python のスクリプトが読む、ということは普通に起きる。
そのときに型の推測をパーサー任せにすると、パーサーの版の違いがそのまま値の違いになります。
引用符で囲めば、どちらの規則でも文字列になる。
書く量は少し増えますが、読む側を選ばなくなるほうを取りたいです。
結び
YAML で、型を変えられたくない値(国コード、0 で始まる番号、時刻、バージョン番号、ハッシュ)は引用符で囲んでください。
引用符の無い値は、パーサーが YAML 1.1 と 1.2 のどちらの規則で読むかによって、真偽値にも8進数にも60進数にもなります。
本記事はAI補助で執筆した、個人開発の紹介記事です。