← 記事一覧

JSON vs JavaScript オブジェクト: シングルクオートが許されない理由

多くの開発者は JS オブジェクトリテラルを JSON と同一視する。同じではない: シングルクオート、引用符なしキー、末尾カンマ、undefined、NaN —— 全ての違いを例で示す。

妥当そうに見えるデータを JSON パーサに貼ると、即座に SyntaxError: Unexpected token ''' が投げられる。そのデータは JavaScript のソースコード由来で完全に妥当に見える —— しかし JSON と JavaScript のオブジェクトリテラルは同じフォーマットではなく、その差は多くの開発者が意識する以上に重要です。

短い答え:JSON は一重引用符をサポートしない

JSON では一重引用符は完全に不正。すべての文字列 —— キーも値も —— は二重引用符を使わなければなりません。例外はありません。

// ❌ 不正な JSON —— 一重引用符の文字列
{ 'name': 'Ada Lovelace', 'active': true }

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

各環境で見るエラー:

// Chrome / Node.js (V8)
SyntaxError: Unexpected token "'", "{'name':..." is not valid JSON

// Firefox
SyntaxError: JSON.parse: unexpected character at line 1 column 2 of the JSON data

// Safari
SyntaxError: JSON Parse error: Single quotes (') are not allowed in JSON

Safari のメッセージが最も親切 —— 問題が何かを直接伝えてくれます。他は予期しない文字を報告するだけ。

なぜ JSON は二重引用符を要求するのか?

JSON は 2001 年に Douglas Crockford によって定義され、RFC 8259 として標準化されました。文法はちょうど一つの文字列区切り、二重引用符(U+0022)を指定。一重引用符は文法にそもそも存在しません。

理由はシンプルさ。JSON は JavaScript だけでなく、どの言語からもパースされることを意図したデータ交換フォーマットです。引用符スタイルを一つに強制することで、曖昧さの一クラスを丸ごと排除。Go、Python、Java、Rust など他のすべての言語のパーサがまったく同じ規則を実装します。

JSON vs JavaScript オブジェクトリテラル:完全な差分

一重引用符は数多くの違いの一つに過ぎません。全体像:

1. キーは二重引用符付き文字列でなければならない

// ❌ JavaScript オブジェクトリテラル —— 裸のキー、一重引用符キー
{ name: "Ada", 'score': 98 }

// ✅ JSON —— すべてのキーは二重引用符付き文字列
{ "name": "Ada", "score": 98 }

JavaScript オブジェクトリテラルは裸の識別子、一重引用符文字列、二重引用符文字列をキーとして受け付けます。JSON は二重引用符文字列のみ。

2. 末尾カンマ不可

// ❌ 不正な JSON
{ "name": "Ada", "score": 98, }
//                            ^ 末尾カンマ

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

モダン JavaScript は明示的に末尾カンマを許可。JSON は不可 —— JSON の末尾カンマに関する完全ガイドを参照

3. コメントなし

// ❌ 不正な JSON
{
  // ユーザプロファイル
  "name": "Ada"
}

// ✅ JSON にコメント構文はない
{ "name": "Ada" }

JSON にはコメント構文がまったくありません。設定ファイルでコメントが必要なら、VS Code 設定、TypeScript の tsconfig.json、いくつかの設定ファイルパーサがサポートする JSONC 形式(JSON-with-Comments)を検討。

4. undefined、関数、Symbol なし

// JavaScript オブジェクト —— シリアライズ不能な値を受け付ける
const obj = {
  fn: () => 42,         // 関数
  sym: Symbol('id'),    // Symbol
  val: undefined,       // undefined
};

// JSON.stringify はそれらを黙って落とすか変換
JSON.stringify(obj);
// → '{}'   (3 つのプロパティが消えるか null になる)

JSON が対応する値型はちょうど 6 つ:string、number、boolean(true/false)、null、object、array。関数、Symbol、undefined に JSON 表現はありません。JSON.stringify() はそれらの値を持つオブジェクトプロパティを黙って落とし、配列内では null に変換します。

5. NaNInfinity なし

JSON.stringify({ ratio: NaN, limit: Infinity });
// → '{"ratio":null,"limit":null}'
// NaN と Infinity は null になる —— 黙って!

JSON.parse('{"ratio": NaN}');
// → SyntaxError: Unexpected token 'N'

NaNInfinity は妥当な JavaScript 数値ですが、JSON の数値仕様の一部ではありません。JSON.stringify() は出る時に null に変換、JSON.parse() は入る時に拒否。この非対称性は微妙なバグの一般的な原因。

6. 数値:16 進数なし、先頭ゼロなし、+ なし

// ❌ 不正な JSON
{ "flags": 0xFF, "code": 007, "delta": +1.5 }

// ✅ 妥当な JSON
{ "flags": 255, "code": 7, "delta": 1.5 }

JSON の数値は 10 進で、明示的な + 符号で始められず、先頭ゼロもダメ(小数点前の単一桁 0 を除く)。16 進数や 8 進数のリテラルは妥当な JSON ではありません。

7. 複数行文字列なし

// ❌ 不正な JSON —— 文字列内のリテラル改行
{ "bio": "Line one
Line two" }

// ✅ エスケープシーケンスを使う
{ "bio": "Line one\nLine two" }

8. 計算キー、Symbol キー、メソッド省略記法

JSON のように見えて相当物がない JS 専用の機能をさらに 3 つ:

// ❌ 計算プロパティ名 —— 評価される、リテラルではない
const k = "id";
const obj = { [k]: 1, [`user_${k}`]: 1 };
// JSON に式はない;キーはリテラルな二重引用符文字列でなければならない。

// ❌ Symbol キー —— JSON.stringify に黙って落とされる
const tag = Symbol('tag');
JSON.stringify({ [tag]: 'admin' });  // → '{}'

// ❌ メソッド省略記法 —— 関数は落とされる/省略される
const obj = { greet() { return 'hi'; } };
JSON.stringify(obj);  // → '{}'

これらのいずれかが必要なら、オブジェクトの JSON 形状の射影をシリアライズしましょう —— JSON.stringify() は常に文字列キーと 6 つの JSON 値型しか生み出しません。

クイック変換チートシート

JavaScript オブジェクトリテラル妥当な JSON 同等物
{ name: "Ada" }{"name":"Ada"}
{ 'name': 'Ada' }{"name":"Ada"}
{ a: 1, }{"a":1}
{ val: undefined }{}(プロパティ脱落)
{ n: NaN }{"n":null}
{ n: Infinity }{"n":null}
{ fn: () => 1 }{}(プロパティ脱落)
0xFF255

JS オブジェクトを JSON に変換する最も安全な方法

引用符を手作業で書き換えて JavaScript オブジェクトを JSON に変換しないでください。JSON.stringify() を使えば、すべての境界条件を正しく扱います:

const obj = { name: 'Ada', score: 98, active: true };

// コンパクト
JSON.stringify(obj);
// → '{"name":"Ada","score":98,"active":true}'

// 2 スペースインデントで pretty-print
JSON.stringify(obj, null, 2);
// → '{
  "name": "Ada",
  "score": 98,
  "active": true
}'

JSON.stringify() の第二引数は replacer —— キー名の配列を渡してそれらのプロパティのみを含めたり、関数を渡して値を変換できます:

// 特定のキーのみ含める
JSON.stringify(obj, ['name', 'score'], 2);
// → '{
  "name": "Ada",
  "score": 98
}'

// カスタム replacer —— undefined を null に変換
JSON.stringify({ a: 1, b: undefined }, (key, value) =>
  value === undefined ? null : value
);
// → '{"a":1,"b":null}'

JS オブジェクトリテラルが文字列で来た場合

JSON のように見えて実は JavaScript オブジェクトリテラルなデータを受け取ることがあります —— ログファイル、デバッグツール、仕様に従っていない API などから。その場合 JSON.parse() は失敗し、選択肢は二つ:

  • 修復パーサを使う —— 一重引用符を二重引用符に変換、末尾カンマを除去、裸キーに引用符を付与、その他のすべての違いを自動で処理。信頼できない入力には最も安全。
  • eval() または Function() を使う —— 妥当な JavaScript には技術的に動作しますが、ソースが完全に信頼できない限り深刻なセキュリティリスク。ユーザ提供入力では絶対にやらないこと。

よくある質問

JSON は一重引用符をサポート?

いいえ。JSON 文法(RFC 8259)はちょうど一つの文字列区切り —— 二重引用符 —— を定義。キーも値も二重引用符を使わなければならず、一重引用符は SyntaxError を投げます。

JSON と JavaScript オブジェクトの違いは?

JSON は厳格なテキスト形式、JavaScript オブジェクトリテラルは寛容なコード。JS は一重引用符、引用符なしキー、末尾カンマ、コメント、undefinedNaN、関数、16 進数を許す —— どれも妥当な JSON ではありません。完全なチートシートは上を、入門は JSON とは?

JavaScript オブジェクトを JSON に変換するには?

JSON.stringify() を使う —— 引用符を手で編集しない。すべての境界条件を扱い、シリアライズ不能な値を落とし、文字列を正しくエスケープします。

なぜ eval() で JS オブジェクトリテラルをパースできない?

eval() は任意コードを実行するため、信頼できない入力には深刻なセキュリティリスク。代わりに修復パーサ(または JSON Fix)を使って JS オブジェクトリテラルを安全に妥当な JSON に変換しましょう。

一重引用符を即修正

今すぐ妥当な JSON に変換したい JavaScript オブジェクトリテラルがあるなら、JSON Fix が変換を自動で行います。一重引用符を二重に変換、裸キーに引用符を付け、末尾カンマを取り、上記の他のすべての差分を修復 —— ブラウザ内で、サーバには何も送られません。