← 記事一覧

JavaScript で壊れた JSON を扱う方法

現実の JSON は乱れがち: 末尾カンマ、シングルクオート、Python リテラル、markdown フェンス。一般的なパターン、安全パースヘルパーの書き方、専用の修復ライブラリを使うべきタイミングを学ぶ。

どんな JavaScript 開発者も見たことがある:JSON.parse()SyntaxError を投げて、アプリケーションがクラッシュ。その JSON は API、設定ファイル、ユーザのクリップボードから来た —— そしてパースできない。本ガイドは、なぜそうなるか、現場の壊れた JSON はどう見えるか、どうやって上手く対処するかを順に解説します —— シンプルな try/catch から自動修復まで。

1. なぜ JSON.parse() が失敗するか

JSON 標準(RFC 8259)は意図的に厳格です。JSON.parse() はそれを忠実に実装 —— どんな小さな逸脱でも、部分的な回復もなく SyntaxError を投げます:

try {
  const data = JSON.parse('{ name: "Ada" }'); // 引用符なしキー
} catch (err) {
  console.error(err.message);
  // SyntaxError: Expected property name or '}' in JSON at position 2
}

HTML をパースするブラウザ(エラー回復モデルが組み込まれている)と違い、JSON パーサは硬いゲートです。厳格パースは特徴:プロデューサにクリーンなデータを出すよう強制します。しかしデータソースを制御できないとき —— LLM 出力、手編集設定ファイル、サードパーティ API —— ゲートが閉まったときの戦略が必要です。

2. 壊れた JSON の最も一般的なパターン

修復ライブラリに手を伸ばす前に、相手を認識すると役立ちます。現実世界の壊れた JSON はほぼ次のいずれかに分類されます:

二重引用符の代わりに一重引用符

// ❌ 不正な JSON
{ 'name': 'Ada Lovelace' }

// ✅ 妥当
{ "name": "Ada Lovelace" }

JavaScript オブジェクトリテラルは一重引用符を受け付けるが JSON は受け付けない。開発者がオブジェクトリテラルを JSON コンテキストに直接コピーし、このエラーに延々と当たる。

末尾カンマ

// ❌ 不正な JSON
{
  "name": "Ada",
  "active": true,   // 末尾カンマ
}

// ✅ 妥当
{
  "name": "Ada",
  "active": true
}

モダン JavaScript は配列とオブジェクトで末尾カンマを許可。JSON は許可しない。これは手編集 JSON ファイルで最も多い単一エラー。

引用符なしのオブジェクトキー

// ❌ 不正な JSON
{ name: "Ada", score: 98 }

// ✅ 妥当
{ "name": "Ada", "score": 98 }

JavaScript オブジェクトリテラルは引用符付きキーを要求しない。JSON はすべてのキーを二重引用符付き文字列とする、例外なし。

JavaScript コメント

// ❌ 不正な JSON
{
  // ユーザレコード
  "name": "Ada",
  /* 2024 追加 */
  "active": true
}

JSON にはコメント構文がない。コメントは設定ファイルに加えられることがある(JSONC —— コメント付き JSON —— は人気の拡張)が、JSON.parse() はそれらを拒否する。

複数行文字列

// ❌ 不正な JSON
{
  "description": "line one
  line two"
}

// ✅ 妥当 —— 改行をエスケープ
{
  "description": "line one\nline two"
}

JSON の文字列値は単一行でなければならない。文字列内のリテラル改行は禁止;代わりに \n エスケープシーケンスを使う。

NaN、Infinity、undefined

// ❌ 妥当な JSON ではない
{ "ratio": NaN, "limit": Infinity, "value": undefined }

// ✅ 妥当な代替
{ "ratio": null, "limit": 1e308, "value": null }

これらは妥当な JavaScript 値だが JSON 仕様の一部ではない。JSON.stringify() は黙って変換(NaNInfinitynull に;undefined のプロパティは丸ごと落とされる)し、微妙なラウンドトリップバグを引き起こすことがある。

Python リテラル

// ❌ Python スタイル —— 不正な JSON
{ "active": True, "deleted": False, "value": None }

// ✅ 妥当な JSON
{ "active": true, "deleted": false, "value": null }

JSON は複数言語間の相互運用を念頭に設計された。Python の大文字始まりの TrueFalseNone は言語横断データバグの頻発源。

3.「修復」と「パース」は同じ操作ではない

根本的に異なる二つの操作を区別することが大事:

  • 厳格パース —— 入力が完全に正しい JSON であることを検証、任意のエラー位置を厳密に報告、逸脱を拒否。データプロデューサを制御していて契約を強制したい場面に。
  • 修復パース —— ヒューリスティック規則のセットを使い、構文的に不完全な入力から妥当な JSON 値の回復を試みる。信頼できないまたは不正確なソース(LLM 出力、ユーザのクリップボード、レガシーサービス)に。

修復パーサは仮定をする。True に遭遇したら true のつもりだったと仮定。末尾カンマに遭遇したら除去。これらの仮定は上記パターンに対してほぼ常に正しい —— しかし「修正」が意図された意味を変えてしまう、本当に不正なドキュメントを静かにマスクすることもある。

正しいツールはコンテキストに依存する。スキーマ検証された API レスポンスパイプラインでは厳格パースを望むので、悪いデータがすぐに表面化する。誰かが Slack メッセージからコピーした JSON を貼り付けている開発者ツールでは、修復パースが時間を節約する。

知っておく価値のあるライブラリ

二つの npm パッケージが重労働の大半を担い、補完的なトレードオフを持つ:

  • jsonrepair —— 寛容な修復パーサ。悪い JSON を渡すとベストエフォートで妥当な文字列が返る。上記パターン(一重引用符、末尾カンマ、引用符なしキー、コメント、Python リテラル、markdown フェンス、未閉じ括弧、部分入力)を扱う。同期、依存ゼロ、小さい —— メモリに完全ドキュメントがある時に理想的。
  • clarinet —— SAX 風ストリーミング JSON パーサ。JSON がチャンクで届く時(ストリーム、大きなファイル、LLM のトークン単位レスポンス)にキー/値が現れるたびに反応したい場合に使う。構文に厳格なので、ソースが信頼できなければ jsonrepair と組み合わせる。

経験則:小さく信頼できない blob があれば 修復;ドキュメントが大きいか段階的に到着するなら ストリーム

4. エラーを捕捉してユーザに親切なフィードバックを

最小限実用なパターンは try/catch。常にこれを行う —— JSON.parse() を未捕捉で投げさせないこと:

function safeParseJson(text) {
  try {
    return { ok: true, value: JSON.parse(text) };
  } catch (err) {
    return { ok: false, error: err.message };
  }
}

const result = safeParseJson(userInput);
if (!result.ok) {
  showError(`Could not parse JSON: ${result.error}`);
}

JSON.parse() のエラーメッセージは JavaScript エンジンによって異なる。V8(Node.js、Chrome)は位置のヒントを含む:

// V8 のエラーメッセージ
"Expected ',' or '}' after property value in JSON at position 42"

// Firefox SpiderMonkey
"JSON.parse: expected ',' or '}' after property value in object
at line 3 column 5 of the JSON data"

位置情報は有用だがエンジン固有。環境横断で信頼できる行/列の報告が必要なら、独自の再帰下降パーサが構造化エラーを発行でき、ユーザに表示しやすい。

5. 自動修復するか確認を求めるか

すべての修復がリスクが同じではない。実用的なヒューリスティック:

  • 自動修復が安全 —— 末尾カンマ、ホワイトスペース正規化、引用符スタイル変換、引用符なしキー、JavaScript コメント、Python の boolean/null リテラル。意味的曖昧さのない機械的変換。
  • ユーザに尋ねることを検討 —— 構造的修復、たとえば未閉じオブジェクト/配列の自動クローズ({"name": "Ada"{"name": "Ada"})、あるいは意図的に見えるコメントの除去。修復は多分正しいが、ユーザが確認したいかも。
  • 常にフラグ、決して静かに修正しない —— NaN → null のような型強制はデータ値を変える。ユーザに何が変わったか見せる。

良い修復 UI は diff を表示する:左に元の入力、右に修復された出力。ユーザは結果をコピー/送信する前に修正が正しいか確認できる。

6. ブラウザ内 JSON 処理のプライバシー上の利点

ブラウザで JSON をローカル処理する —— ページを動かしている JavaScript エンジンを使う —— と、データはユーザのマシンを出ない。これは開発者がしばしば認識する以上に重要:

  • API キーと資格情報 —— 開発者は秘密を含む JSON ペイロードを定期的に貼る。サーバサイドツールはあらゆるリクエストをログする。
  • PII と健康データ —— データ転送がない時、GDPR と HIPAA の準拠は格段に簡単になる。
  • 企業データ —— 多くのセキュリティポリシーは社内データをサードパーティ web サービスへ貼り付けることを禁ずる。

ブラウザ内処理はゼロリスクの JSON 修復を提供する:サーバなし、ログなし、データ保持なし。計算は <textarea> と数 KB の JavaScript で行われる。

よくある質問

JavaScript で JSON.parse エラーをどう処理する?

呼び出しを try/catch で包み、SyntaxError を伝播させる代わりに構造化結果を返す(上の safeParseJson ヘルパー参照)。信頼できない入力に対してガードなしで JSON.parse() を呼ばないこと。

壊れた JSON を自動でパースする方法はある?

ある —— 修復パーサがヒューリスティック(末尾カンマ除去、一重引用符変換、True の小文字化)を適用して不完全な入力から妥当な値を回復する。LLM 出力や貼り付けテキストのような信頼できないソースに使う;契約検証された API レスポンスでは悪いデータを表面化させたいので避ける。

JSON を修復するか拒否するかいつ判断?

人が近似的な JSON を貼ったり LLM が生成したりした時は修復;プロデューサを制御してスキーマを強制したい時は拒否(厳格パース)。NaN → null のような型強制は常にフラグを立て、静かに適用しない。

壊れた JSON の最も一般的な原因は?

JavaScript オブジェクトリテラルを JSON として扱うこと —— 一重引用符、引用符なしキー、末尾カンマ。JSON vs JavaScript オブジェクト[object Object] とその他のエラー の構文エラーリファレンスを参照。

7. 今試そう —— 壊れた JSON を貼り付けて

修正が必要な不正な JSON 文字列があるなら、JSON Fix が上記のすべてのパターンを扱う —— 一重引用符、末尾カンマ、引用符なしキー、Python リテラル、コメント、markdown コードフェンス等。すべてブラウザで実行;サーバには何も送信されない。