このページは FormatArc のブログ記事が引用している実測の生データです。検索結果には表示されません (noindex)。
| File | Size |
|---|---|
| README.md | 4.1 KB |
| cases.json | 964 B |
| measure.mjs | 6.4 KB |
| package-lock.json | 985 B |
| package.json | 236 B |
| python-runner.py | 771 B |
| results.json | 9.0 KB |
# JSON trailing comma acceptance and error messages (5 parsers, measured)
`json-trailing-comma` 記事が引用するパーサー別エラーメッセージ・行番号の一次ソース。
末尾カンマ (trailing comma) を含む JSON を主要 5 パーサーに実際に食わせ、**受理するか
どうか・実際に出るエラーメッセージ文字列・報告される行/列/offset** を記録する。
## 測定対象パーサー
| パーサー | 呼び方 | バージョン |
| --- | --- | --- |
| V8 (`JSON.parse`) | Node.js 組み込み | Node 実行系のバージョンに追従 (measure.mjs 内で `process.version` を記録) |
| Python (`json.loads`) | `python3 python-runner.py` をサブプロセスで呼ぶ | `python3 --version` を記録 |
| `jq` | インストールされていれば `jq .` にケースを標準入力で渡す | `jq --version` を記録。無ければ `available: false` |
| JSON5 (`json5` npm package) | `JSON5.parse` | package.json 固定 (2.2.3) |
| JSONC (`jsonc-parser` npm package, VS Code が使うパーサー) | `jsonc.parse` + `errors` 配列 | package.json 固定 (3.3.1) |
V8 は FormatArc の json-formatter ツールが実際に使っているパーサー (`JSON.parse` / `JSON.stringify`)。
## ケース (cases.json)
1. `object-trailing` — オブジェクトの最後のメンバーの後の末尾カンマ
2. `array-trailing` — 配列の最後の要素の後の末尾カンマ
3. `nested-trailing` — ネストした配列内の末尾カンマ (外側は正しいカンマ)
4. `multiline-object-trailing` — 複数行オブジェクトでの末尾カンマ (行番号が意味を持つケース)
5. `double-trailing` — カンマが2つ連続する壊れ方 (`[1, 2,,]`)
6. `valid-baseline` — 末尾カンマ無し。全パーサーが受理することを確認する陰性対照
## `accepted` の定義
- V8 / Python / jq / JSON5 は「例外を投げずにパースが完了したか」で判定する。
- `jsonc-parser` の `parse()` はエラー回復 (lenient recovery) を行う実装で、末尾カンマが
あっても**例外を投げず値を返す**。そのため本ベンチでは `accepted = (errors.length === 0)`
と定義し、`errors` 配列 (`jsonc.printParseErrorCode` で文字列化したもの) をそのまま
`errors` フィールドに残す。値が返ってきたかどうかとは別軸であることに注意。
## 再現手順
```bash
cd scripts/benchmarks/json-trailing-comma
npm install
node measure.mjs # Python 側は python-runner.py をサブプロセスで自動的に呼ぶ
```
`results.json` が上書き生成される。標準出力にも同じ JSON が出る。
## 測定のスコープと限界
- **バージョン依存**: 各パーサーの挙動・エラーメッセージ文言はバージョンで変わりうる。
`results.json` の `environment` に記録した実測時のバージョンでのみ保証する
(measure.mjs 実行時点で固定されている `json5` / `jsonc-parser` を除き、V8 / Python / jq
は実行系にインストールされているものをそのまま使う)。
- **jq 未インストール環境**: `environment.jqAvailable` が `false` になり、各ケースの
jq 側エントリは `{ "available": false, "accepted": null, "message": null, "position": null }`
で記録される。推測で埋めない。
- **jsonc-parser の `accepted`**: 上記の通り「エラー配列が空かどうか」であり、値が
返ってきたかどうかとは別。記事本文で引用する際は取り違えないこと。
- **位置情報の形式が揃わない**: V8 は `position` (offset) と `line/column` の両方または
片方のみを返す (ケースによって message の書式自体が変わる)。jq / Python は
line/column、jsonc-parser は offset のみで line/column を返さない。フォーマット横断の
「行番号」比較には JSON5/V8/Python/jq いずれもエラーメッセージ全体を引用するのが安全。
記事本文の表は `results.json` の値と完全一致させること (数値・文言の捏造禁止ルール準拠)。
{
"generatedAt": "2026-08-17T01:48:24.055Z",
"environment": {
"node": "v26.7.0",
"python": "3.14.6",
"jq": "1.7.1-apple",
"jqAvailable": true,
"json5": "2.2.3",
"jsoncParser": "3.3.1",
"os": "Darwin 25.5.0",
"machine": "Apple M5 Pro"
},
"cases": [
{
"id": "object-trailing",
"description": "Trailing comma after the last member of an object",
"input": "{\"a\": 1, \"b\": 2,}",
"parsers": {
"V8 (Node.js v26.7.0) JSON.parse": {
"accepted": false,
"message": "Expected double-quoted property name in JSON at position 16 (line 1 column 17)",
"position": {
"offset": 16,
"line": 1,
"column": 17
}
},
"Python 3.14.6 json.loads": {
"accepted": false,
"message": "Illegal trailing comma before end of object: line 1 column 16 (char 15)",
"position": {
"line": 1,
"column": 16,
"offset": 15
}
},
"jq jq-1.7.1-apple": {
"accepted": false,
"message": "jq: parse error: Expected another key-value pair at line 1, column 17",
"position": {
"line": 1,
"column": 17,
"offset": null
}
},
"json5 2.2.3": {
"accepted": true,
"message": null,
"position": null
},
"jsonc-parser 3.3.1": {
"accepted": false,
"message": "PropertyNameExpected at offset 16; ValueExpected at offset 16",
"position": {
"offset": 16,
"line": null,
"column": null
},
"errors": [
{
"error": "PropertyNameExpected",
"offset": 16,
"length": 1
},
{
"error": "ValueExpected",
"offset": 16,
"length": 1
}
]
}
}
},
{
"id": "array-trailing",
"description": "Trailing comma after the last element of an array",
"input": "[1, 2, 3,]",
"parsers": {
"V8 (Node.js v26.7.0) JSON.parse": {
"accepted": false,
"message": "Unexpected token ']', \"[1, 2, 3,]\" is not valid JSON",
"position": null
},
"Python 3.14.6 json.loads": {
"accepted": false,
"message": "Illegal trailing comma before end of array: line 1 column 9 (char 8)",
"position": {
"line": 1,
"column": 9,
"offset": 8
}
},
"jq jq-1.7.1-apple": {
"accepted": false,
"message": "jq: parse error: Expected another array element at line 1, column 10",
"position": {
"line": 1,
"column": 10,
"offset": null
}
},
"json5 2.2.3": {
"accepted": true,
"message": null,
"position": null
},
"jsonc-parser 3.3.1": {
"accepted": false,
"message": "ValueExpected at offset 9",
"position": {
"offset": 9,
"line": null,
"column": null
},
"errors": [
{
"error": "ValueExpected",
"offset": 9,
"length": 1
}
]
}
}
},
{
"id": "nested-trailing",
"description": "Trailing comma inside a nested array, valid comma at the outer level",
"input": "{\"a\": [1, 2,], \"b\": 3}",
"parsers": {
"V8 (Node.js v26.7.0) JSON.parse": {
"accepted": false,
"message": "Unexpected token ']', ...\"a\": [1, 2,], \"b\": 3}\" is not valid JSON",
"position": null
},
"Python 3.14.6 json.loads": {
"accepted": false,
"message": "Illegal trailing comma before end of array: line 1 column 12 (char 11)",
"position": {
"line": 1,
"column": 12,
"offset": 11
}
},
"jq jq-1.7.1-apple": {
"accepted": false,
"message": "jq: parse error: Expected another array element at line 1, column 13",
"position": {
"line": 1,
"column": 13,
"offset": null
}
},
"json5 2.2.3": {
"accepted": true,
"message": null,
"position": null
},
"jsonc-parser 3.3.1": {
"accepted": false,
"message": "ValueExpected at offset 12",
"position": {
"offset": 12,
"line": null,
"column": null
},
"errors": [
{
"error": "ValueExpected",
"offset": 12,
"length": 1
}
]
}
}
},
{
"id": "multiline-object-trailing",
"description": "Trailing comma after the last property in a multi-line object, so line numbers are meaningful",
"input": "{\n \"a\": 1,\n \"b\": 2,\n}",
"parsers": {
"V8 (Node.js v26.7.0) JSON.parse": {
"accepted": false,
"message": "Expected double-quoted property name in JSON at position 22 (line 4 column 1)",
"position": {
"offset": 22,
"line": 4,
"column": 1
}
},
"Python 3.14.6 json.loads": {
"accepted": false,
"message": "Illegal trailing comma before end of object: line 3 column 9 (char 20)",
"position": {
"line": 3,
"column": 9,
"offset": 20
}
},
"jq jq-1.7.1-apple": {
"accepted": false,
"message": "jq: parse error: Expected another key-value pair at line 4, column 1",
"position": {
"line": 4,
"column": 1,
"offset": null
}
},
"json5 2.2.3": {
"accepted": true,
"message": null,
"position": null
},
"jsonc-parser 3.3.1": {
"accepted": false,
"message": "PropertyNameExpected at offset 22; ValueExpected at offset 22",
"position": {
"offset": 22,
"line": null,
"column": null
},
"errors": [
{
"error": "PropertyNameExpected",
"offset": 22,
"length": 1
},
{
"error": "ValueExpected",
"offset": 22,
"length": 1
}
]
}
}
},
{
"id": "double-trailing",
"description": "Two consecutive commas before the closing bracket",
"input": "[1, 2,,]",
"parsers": {
"V8 (Node.js v26.7.0) JSON.parse": {
"accepted": false,
"message": "Unexpected token ',', \"[1, 2,,]\" is not valid JSON",
"position": null
},
"Python 3.14.6 json.loads": {
"accepted": false,
"message": "Expecting value: line 1 column 7 (char 6)",
"position": {
"line": 1,
"column": 7,
"offset": 6
}
},
"jq jq-1.7.1-apple": {
"accepted": false,
"message": "jq: parse error: Expected value before ',' at line 1, column 7",
"position": {
"line": 1,
"column": 7,
"offset": null
}
},
"json5 2.2.3": {
"accepted": false,
"message": "JSON5: invalid character ',' at 1:7",
"position": {
"line": 1,
"column": 7,
"offset": null
}
},
"jsonc-parser 3.3.1": {
"accepted": false,
"message": "ValueExpected at offset 6; ValueExpected at offset 7",
"position": {
"offset": 6,
"line": null,
"column": null
},
"errors": [
{
"error": "ValueExpected",
"offset": 6,
"length": 1
},
{
"error": "ValueExpected",
"offset": 7,
"length": 1
}
]
}
}
},
{
"id": "valid-baseline",
"description": "No trailing comma — negative control that every parser must accept",
"input": "{\"a\": 1, \"b\": 2}",
"parsers": {
"V8 (Node.js v26.7.0) JSON.parse": {
"accepted": true,
"message": null,
"position": null
},
"Python 3.14.6 json.loads": {
"accepted": true,
"message": null,
"position": null
},
"jq jq-1.7.1-apple": {
"accepted": true,
"message": null,
"position": null
},
"json5 2.2.3": {
"accepted": true,
"message": null,
"position": null
},
"jsonc-parser 3.3.1": {
"accepted": true,
"message": null,
"position": null
}
}
}
]
}