yaml-parser-differences

このページは FormatArc のブログ記事が引用している実測の生データです。検索結果には表示されません (noindex)。

Files

FileSize
README.md2.5 KB
cases.json1.2 KB
measure.mjs2.2 KB
measure.py2.4 KB
package-lock.json1.4 KB
package.json234 B
results-node.json2.5 KB
results-python.json2.4 KB
results.json6.5 KB

README.md

# YAML parser behaviour differences (YAML 1.1 vs 1.2 ambiguous scalars)

`yaml-json-difference` 記事の「N-parser 実測差分表」の一次ソース。曖昧な YAML スカラー
(Norway problem / on・yes / 8進数 / 60進数 / 素の日付 / 重複キー / merge key 等) を
主要 4 パーサーがどう解決するかを実測する。

## 測定対象パーサー

| パーサー | バージョン | 実行系 | 既定の解決規則 |
| --- | --- | --- | --- |
| js-yaml (`load`, DEFAULT_SCHEMA) | 4.1.1 | Node v26.3.1 | ほぼ YAML 1.2 + timestamp 独自対応 |
| yaml (eemeli, `parse` defaults) | 2.8.3 | Node v26.3.1 | YAML 1.2 core schema |
| PyYAML (`safe_load`) | 6.0.3 | Python 3.14.6 | YAML 1.1 |
| ruamel.yaml (`YAML(typ="safe")`) | 0.19.1 | Python 3.14.6 | YAML 1.2 |

実行環境: Apple M5 Pro / macOS (Darwin 25.5.0)。

## 再現手順

```bash
cd scripts/benchmarks/yaml-parser-differences

# Node 側 (js-yaml + eemeli yaml) → results-node.json
npm install
node measure.mjs

# Python 側 (PyYAML + ruamel.yaml) → results-python.json
python3 -m venv venv
./venv/bin/pip install pyyaml ruamel.yaml
./venv/bin/python measure.py
```

`results.json` は両者を統合した確定値 (テストケース定義は `cases.json`)。
記事本文の表の数値・型はすべて `results.json` と一致させること。

## 実測結果の要点 (2026-07-10 測定)

- `value: NO` を boolean `false` に解決するのは **PyYAML だけ**。js-yaml / eemeli yaml /
  ruamel.yaml は文字列 `"NO"` を返す (Norway problem は「YAML の問題」ではなく
  「YAML 1.1 系パーサーの問題」)
- `on` / `yes` も同様に PyYAML のみ boolean
- YAML 1.1 の単文字 `y` は 4 パーサーとも文字列 (仕様にあるが実装は追随していない)
- `010` は PyYAML が 8 進数として `8`、他 3 者は 10 進数 `10`
- `0o10` (YAML 1.2 の 8 進表記) は PyYAML だけ文字列 `"0o10"`、他 3 者は `8`
- `1:30` (60 進数) は PyYAML だけ `90`
- 素の日付 `2026-07-10` は eemeli yaml だけ文字列。js-yaml は JS の `Date`、
  PyYAML / ruamel.yaml は `datetime.date` に変換する (JSON 変換時の型崩れ要因)
- 重複キーは PyYAML だけ last-wins (`{"a": 2}`)、他 3 者はエラー
- merge key `<<` は eemeli yaml だけ既定で解決しない (`"<<"` という名前のキーとして残る。
  `parse(src, { merge: true })` の明示が必要)。他 3 者は既定でマージする
- `.inf` / `~` は 4 パーサーとも一致 (Infinity / null)

results.json

{
  "topic": "YAML parser behaviour differences (YAML 1.1 vs 1.2 ambiguous scalars)",
  "measuredAt": "2026-07-10",
  "environment": {
    "machine": "Apple M5 Pro (macOS, Darwin 25.5.0)",
    "node": "v26.3.1",
    "python": "3.14.6"
  },
  "cases": [
    {
      "id": "norway-NO",
      "yaml": "value: NO",
      "question": "YAML 1.1 boolean (Norway problem) or string?"
    },
    {
      "id": "on-keyword",
      "yaml": "value: on",
      "question": "YAML 1.1 on/off boolean or string?"
    },
    {
      "id": "yes-keyword",
      "yaml": "value: yes",
      "question": "YAML 1.1 yes/no boolean or string?"
    },
    {
      "id": "single-y",
      "yaml": "value: y",
      "question": "YAML 1.1 spec lists y/n as boolean — do parsers follow?"
    },
    {
      "id": "octal-legacy",
      "yaml": "value: 010",
      "question": "YAML 1.1 octal (8) or decimal 10 or string?"
    },
    {
      "id": "octal-12",
      "yaml": "value: 0o10",
      "question": "YAML 1.2 octal notation (8) or string?"
    },
    {
      "id": "sexagesimal",
      "yaml": "value: 1:30",
      "question": "YAML 1.1 sexagesimal (90) or string?"
    },
    {
      "id": "bare-date",
      "yaml": "value: 2026-07-10",
      "question": "timestamp/date object or string?"
    },
    {
      "id": "dot-inf",
      "yaml": "value: .inf",
      "question": "Infinity or string?"
    },
    {
      "id": "duplicate-keys",
      "yaml": "a: 1\na: 2",
      "question": "error, last-wins, or first-wins?"
    },
    {
      "id": "merge-key",
      "yaml": "base: &b\n  x: 1\nchild:\n  <<: *b\n  y: 2",
      "question": "merge key << supported?"
    },
    {
      "id": "tilde-null",
      "yaml": "value: ~",
      "question": "null or string?"
    }
  ],
  "parsers": [
    {
      "parser": "js-yaml 4.1.1 load (DEFAULT_SCHEMA)",
      "results": {
        "norway-NO": {
          "type": "string",
          "repr": "\"NO\""
        },
        "on-keyword": {
          "type": "string",
          "repr": "\"on\""
        },
        "yes-keyword": {
          "type": "string",
          "repr": "\"yes\""
        },
        "single-y": {
          "type": "string",
          "repr": "\"y\""
        },
        "octal-legacy": {
          "type": "number",
          "repr": "10"
        },
        "octal-12": {
          "type": "number",
          "repr": "8"
        },
        "sexagesimal": {
          "type": "string",
          "repr": "\"1:30\""
        },
        "bare-date": {
          "type": "Date",
          "repr": "2026-07-10T00:00:00.000Z"
        },
        "dot-inf": {
          "type": "number",
          "repr": "Infinity"
        },
        "duplicate-keys": {
          "type": "error",
          "repr": "duplicated mapping key (2:1)"
        },
        "merge-key": {
          "type": "object",
          "repr": "{\"base\":{\"x\":1},\"child\":{\"x\":1,\"y\":2}}"
        },
        "tilde-null": {
          "type": "null",
          "repr": "null"
        }
      }
    },
    {
      "parser": "yaml (eemeli) 2.8.3 parse (defaults)",
      "results": {
        "norway-NO": {
          "type": "string",
          "repr": "\"NO\""
        },
        "on-keyword": {
          "type": "string",
          "repr": "\"on\""
        },
        "yes-keyword": {
          "type": "string",
          "repr": "\"yes\""
        },
        "single-y": {
          "type": "string",
          "repr": "\"y\""
        },
        "octal-legacy": {
          "type": "number",
          "repr": "10"
        },
        "octal-12": {
          "type": "number",
          "repr": "8"
        },
        "sexagesimal": {
          "type": "string",
          "repr": "\"1:30\""
        },
        "bare-date": {
          "type": "string",
          "repr": "\"2026-07-10\""
        },
        "dot-inf": {
          "type": "number",
          "repr": "Infinity"
        },
        "duplicate-keys": {
          "type": "error",
          "repr": "Map keys must be unique at line 2, column 1:"
        },
        "merge-key": {
          "type": "object",
          "repr": "{\"base\":{\"x\":1},\"child\":{\"<<\":{\"x\":1},\"y\":2}}"
        },
        "tilde-null": {
          "type": "null",
          "repr": "null"
        }
      }
    },
    {
      "parser": "PyYAML 6.0.3 safe_load",
      "results": {
        "norway-NO": {
          "type": "boolean",
          "repr": "false"
        },
        "on-keyword": {
          "type": "boolean",
          "repr": "true"
        },
        "yes-keyword": {
          "type": "boolean",
          "repr": "true"
        },
        "single-y": {
          "type": "string",
          "repr": "\"y\""
        },
        "octal-legacy": {
          "type": "number",
          "repr": "8"
        },
        "octal-12": {
          "type": "string",
          "repr": "\"0o10\""
        },
        "sexagesimal": {
          "type": "number",
          "repr": "90"
        },
        "bare-date": {
          "type": "date",
          "repr": "2026-07-10"
        },
        "dot-inf": {
          "type": "number",
          "repr": "inf"
        },
        "duplicate-keys": {
          "type": "object",
          "repr": "{\"a\": \"2\"}"
        },
        "merge-key": {
          "type": "object",
          "repr": "{\"base\": \"{'x': 1}\", \"child\": \"{'x': 1, 'y': 2}\"}"
        },
        "tilde-null": {
          "type": "null",
          "repr": "null"
        }
      }
    },
    {
      "parser": "ruamel.yaml 0.19.1 typ=safe",
      "results": {
        "norway-NO": {
          "type": "string",
          "repr": "\"NO\""
        },
        "on-keyword": {
          "type": "string",
          "repr": "\"on\""
        },
        "yes-keyword": {
          "type": "string",
          "repr": "\"yes\""
        },
        "single-y": {
          "type": "string",
          "repr": "\"y\""
        },
        "octal-legacy": {
          "type": "number",
          "repr": "10"
        },
        "octal-12": {
          "type": "number",
          "repr": "8"
        },
        "sexagesimal": {
          "type": "string",
          "repr": "\"1:30\""
        },
        "bare-date": {
          "type": "date",
          "repr": "2026-07-10"
        },
        "dot-inf": {
          "type": "number",
          "repr": "inf"
        },
        "duplicate-keys": {
          "type": "error",
          "repr": "while constructing a mapping"
        },
        "merge-key": {
          "type": "object",
          "repr": "{\"base\": \"{'x': 1}\", \"child\": \"{'x': 1, 'y': 2}\"}"
        },
        "tilde-null": {
          "type": "null",
          "repr": "null"
        }
      }
    }
  ]
}