JSON5 と JSONC:コメント付き JSON
要点:両方の方言が存在するのは、人間が書く設定にコメントと末尾カンマが必要だからです。しかし構文とエコシステムの広がりは異なり、選択を誤るとツールチェーンが詰まります。

要点:VS Code や TypeScript の設定内のコメントには JSONC を使い、単一引用符や引用符なしキーのような ES5.1 リテラル構文が必要なら JSON5 を使い、公開 API は標準 JSON のままにしましょう。
標準 JSON はコメントを許さないので、どのチームもこっそり書いてしまいます。そしてある日ビルドが壊れます。こっそりやるより、適切な方言を選びましょう。
1. 二つの方言は動機が異なる
- JSONC(JSON with Comments):標準 JSON にコメントと末尾カンマを加えたもので、他の構文変更はほとんどありません。VS Code エコシステムの事実上の標準で、
settings.json、tsconfig.json、.eslintrc.jsonはいずれもこれを使います。 - JSON5:はるかに野心的で、ES5.1 のオブジェクトリテラルに直接合わせています。単一引用符、引用符なしキー、16 進数、
Infinity/NaN、バックスラッシュによる行継続、先頭の+記号まで許します。
一文で見分けるなら、JSONC は書くのを楽にするだけ、JSON5 は設定ファイルを JavaScript のように書けるようにする。
2. 構文比較表
| 機能 | 標準 JSON | JSONC | JSON5 |
|---|---|---|---|
コメント // /* */ |
✗ | ✓ | ✓ |
| 末尾カンマ | ✗ | ✓ | ✓ |
| 単一引用符の文字列 | ✗ | ✗ | ✓ |
| 引用符なしキー | ✗ | ✗ | ✓ |
| 16 進数 | ✗ | ✗ | ✓ |
先頭・末尾の小数点 .5 5. |
✗ | ✗ | ✓ |
+ で始まる数値 |
✗ | ✗ | ✓ |
Infinity / NaN |
✗ | ✗ | ✓ |
行末の \ による継続 |
✗ | ✗ | ✓ |
| 複数行文字列 | ✗ | ✗ | ✓(行継続で実現) |
| 任意のトップレベル値 | ✓ | ✓ | ✓ |
JSON5 の仕様全体は一ページ強にすぎません。json5 パッケージは長くコミュニティによって保守され、ブラウザと Node でも同じように動作します。
3. パース:二つの API、二種類の位置情報能力
3.1 JSON5:単純な文書全体のパース
import JSON5 from 'json5';
const config = JSON5.parse(`
{
// サービスのポート
port: 8080,
host: '0.0.0.0',
features: ['json5', 'jsonc',],
retries: 0x10,
}
`);
console.log(config.port, config.retries); // 8080 16
JSON5.parse はオフセット情報を持たない素のオブジェクトを返します。編集の場面を想定しておらず、「設定を読む」ためだけのものだからです。
3.2 JSONC:オフセット付きの構文木
jsonc-parser の価値はパースではなく編集にあります。三層の API を提供します。
| API | 目的 | 典型的な用途 |
|---|---|---|
parse |
offset/length を持つ木にパース |
シンタックスハイライト、折りたたみ、ナビゲーション |
visit |
深さ優先探索。コールバックがノードのパスを受け取る | JSONPath 風の検査 |
modify + applyEdits |
最小限のテキスト編集を生成し、元のテキストに適用 | コメントを保ったまま値を変更 |
import { parseTree, findNodeAtLocation, modify, applyEdits } from 'jsonc-parser';
const text = `{
// ポート設定
"server": {
"port": 8080 // 本番では 80
}
}`;
const tree = parseTree(text);
const portNode = findNodeAtLocation(tree!, ['server', 'port']);
console.log(portNode?.offset, portNode?.length);
const edits = modify(text, ['server', 'port'], 3000, {
formattingOptions: { insertSpaces: true, tabSize: 2 },
});
console.log(applyEdits(text, edits));
出力は元のコメントを保ち、8080 の三文字だけを置き換えます。
{
// ポート設定
"server": {
"port": 3000 // 本番では 80
}
}
これこそが「正規表現で設定を編集する」にできないことです。正規表現はどの 8080 を変えるべきか分からず、文字列の中の 8080 を触ってはいけないことも分かりません。
3.3 エラーメッセージはオフセットを運ぶ必要がある
jsonc-parser のエラーコードは列挙値で、よく使うものは次のとおりです。
| エラーコード | 意味 |
|---|---|
InvalidSymbol |
あるべきでない文字が現れた |
PropertyNameExpected |
キー名を期待していた |
ColonExpected |
コロンを期待していた |
CommaExpected |
カンマを期待していた |
CloseBraceExpected |
オブジェクトが閉じられていない |
UnexpectedEndOfComment |
コメントが閉じられていない |
InvalidEscapeCharacter |
不正なエスケープ |
offset を行と列に変換すれば、エディタは問題の位置に正確に波線を描けます。
4. どちらをいつ使うか
| 場面 | 推奨 | 理由 |
|---|---|---|
| 公開 API のリクエスト / レスポンスボディ | 標準 JSON | 方言は汎用クライアントでサポートされない |
package.json、composer.json |
標準 JSON | パッケージマネージャは標準 JSON しか理解しない |
tsconfig.json、.vscode/settings.json |
JSONC | エコシステムの既定で、ツールチェーンがネイティブ対応 |
| アプリケーション設定(コメントが必要) | JSONC か YAML | ツールが読み書きでき、コメントも保てる |
Infinity、16 進数、複雑なリテラルが必要 |
JSON5 | 表現力が最も高い |
| データ交換、ログ、キャッシュ | 行区切りまたは標準 JSON | ストリーミング性と普遍性が求められる |
経験則:ファイルを「人間以外のツール」が読むなら標準 JSON を優先し、自分のコードだけが読むと確信できるときにだけ方言を検討する。
5. 方言が持ち込む三つのリスク
5.1 要件の漏れ
別のツールが JSON5 の設定ファイルを読み、JSON.parse を呼ぶと即座に落ちます。典型的な症状は「ローカルでは動くのに Docker では失敗する」。コンテナが別の読み込み経路を通るからです。
対策:リポジトリに CI チェックを追加し、方言を使ってよいファイルを明示的に列挙する。それ以外は標準 JSON として検証する。
5.2 型推論まわりの驚き
JSON5 は引用符なしキーと単一引用符を許しますが、日付や正規表現のようなリテラル構文を持ち込めるわけではありません。あくまでデータ形式であり、JavaScript の評価ではありません。
JSON5 が new Date() を表現できると思った人が、パースの失敗に直面しました。JSON5 は eval ではありません。
5.3 コメントが無言で消える
JSON5.stringify も JSON.stringify もコメントを保存しません。ですから「読み込んで、値を一つ直して、書き戻す」とコメントがすべて失われます。
コメントを残す必要があるときは、jsonc-parser の編集 API か、コメント保持に対応した YAML ライブラリしか選択肢がありません。
6. 実践的な推奨
- [ ] チームのドキュメントに「ファイル種別 → 形式」の表を置き、誰も即興で決めないようにする。
- [ ] 設定の読み込みを単一の
loadConfig()に包み、内部でどのパーサーを使うか決める。業務コードが直接JSON.parseを呼ばないようにする。 - [ ] コメント付き設定をプログラムで変更するときは
modify+applyEditsを使い、ファイル全体を書き直さない。 - [ ] 方言ファイルも CI で検証する(JSONC は
jsonc-parser、JSON5 はJSON5.parse)。さもないと構文エラーは実行時にしか現れない。 - [ ] 公開インターフェース、キャッシュキー、メッセージキューのペイロードには標準 JSON を使う。
FAQ
JSON5 と JSONC はどう選べばよいですか?
構文の数ではなく実行環境で選びます。VS Code、TypeScript、ESLint は既定で JSONC を読むため、JSONC の互換性が最も高いです。単一引用符、引用符なしキー、Infinity のような ES5.1 リテラル構文が必要なときにだけ JSON5 に手を伸ばします。どちらの方言も公開 API のレスポンスには適しません。
標準 JSON パーサーが JSONC を直接読めないのはなぜですか?
JSONC はコメントと末尾カンマを許しますが、JSON.parse は RFC 8259 の JSON しか受け付けず、どちらもエラーになります。JSONC を読むには jsonc-parser のようなパーサーが必要か、標準パーサーに渡す前にコメントを除去する必要があります。
JSONC のコメントはファイル編集後も残るべきですか?
残すべきですが、それは編集 API を通してのみ可能です。jsonc-parser の modify と applyEdits は対象ノードを変えつつ残りのコメントを保ちます。オブジェクトにパースしてから文字列化すると、コメントはすべて失われます。