1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

同じYAMLを js-yaml 4 と PyYAML 6 で読んだら、11行のうち値が一致したのは2行だけだった

1
Posted at

背景

自分のツールサイトに、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補助で執筆した、個人開発の紹介記事です。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?