JSON.parse と JSON.stringify の違いと使い分け

JSON(JavaScript Object Notation)は、軽量なテキストベースのデータ交換形式であり、JavaScript のnative構文を元に設計されています。ES5より、JSONオブジェクトが導入され、データのシリアライズ・デシリアライズを扱うための標準APIが提供されました。

JSONオブジェクトは、フロットのメソッドを2つだけ持つ特殊なオブジェクトであり、コンストラクタとして使用できず、属性もありません。

typeof JSON === 'object'; // true

JSON.parse:文字列を値に復元する

JSON.parse(text, reviver?) は、JSON形式の文字列をJavaScriptの値(オブジェクト・配列・数値・文字列・真偽値・null)へ変換します。

JSON.parse('{}');           // {}
JSON.parse('true');         // true
JSON.parse('"hello"');     // "hello"
JSON.parse('123.45');      // 123.45
JSON.parse('null');        // null

引数と動作

  • text:パース対象のJSON文字列(または、数値・真偽値・nullで自動変換)
  • reviver(任意):パース後の値を再帰的に加工するコールバック関数。引数は (key, value) で、返却値がそのキーに対応する実際の値になります。
JSON.parse('10', (k, v) => (k === '' ? v * 2 : v)); // 20

JSON.parse('{"a":[1,2]}', (k, v) => {
  if (k === 'a') return v.map(n => n + 10);
  return v;
});
// { a: [11, 12] }

加工処理の流れ

  1. ネストされた構造を深さ優先で省内側から外側へwalk
  2. 各ノードで reviver(key, value) を呼び出し、返された値で置換
  3. 最終的に key = '' で全体構造に対する最終変換が実行される

許容されるJSONのルール

JSON.parse は、ECMA-404仕様に厳密に準拠した入力のみを受け入れます。

条件 結果
文字列はダブルクォート必須 '"OK"' "OK"
'\"OK\"' ❌ SyntaxError
数値は10進数のみ(0x, NaN等不可) '42e1' 420
'0x2A' ❌ SyntaxError
:: 12.n も不可(小数点の直後に終端不可) '12.'
論理値・nullはリテラルで直接変換可能 JSON.parse(true) SyntaxError(型チェック前)
JSON.parse('true') true
key名はダブルクォート必須 '{name: "T"}'
'{"name":"T"}'
配列/オブジェクト末尾のカンマはNG '[1,2,]'
非JSON構造(undefined, Symbol, 関数)は不可 JSON.parse('undefined')
JSON.parse('function(){}')
JSON.parse('{"\u0061":"A"}'); // {"a":"A"}(ユニコードエスケープは有効)

セキュリティに注意すべき代替手法(非推奨)

// ⛔ 危険!任意コード実行の可能性あり
const unsafeObj = eval('(' + jsonString + ')');
const alsoUnsafe = new Function(`return ${jsonString}`)();

JSON.stringify:値をJSON文字列に変換する

JSON.stringify(value, replacer?, space?) は、JavaScriptの値をJSON文字列にシリアライズします。

JSON.stringify({ name: "Taro" }); // '{"name":"Taro"}'
JSON.stringify([1, 2, 3]);         // '[1,2,3]'

オプションパラメータの詳細

  • replacer
  • null / 非関数 / 非配列:何もしない(デフォルト動作)
  • 配列:指定されたキー名のみをシリアライズ(オブジェクトのみ効果あり)
  • 関数:各キーに合成処理を加えるカスタマイズ可能なフィルタ
  • space:整形出力用インデント指定
  • 数値:1~10の spaces number(例:2 → 2つスペースでインデント)
  • 文字列:インデントに使用するプレフィックス(最大10文字)
  • null:なし(圧縮出力)
JSON.stringify(
  { a: 1, b: [2, 3] },
  ['a'],       // bは除外
  '  '         // 2スペースインデント
);
// '{\n  "a": 1\n}'

replacer関数の挙動

  • 最初の呼び出しでは key = '', value = 全体
  • 子要素ごとに key = プロパティ名, value = 値
  • 戻り値が undefined → そのプロパティは省略(配列要素では null に置換)
JSON.stringify(
  { user: { name: 'Taro', age: 20 } },
  (k, v) => typeof v === 'number' ? v + 10 : v
);
// '{"user":{"name":"Taro","age":30}}'

JSON.stringify(
  { a: 1, b: () => 0 },
  (k, v) => (typeof v === 'function' ? undefined : v)
);
// '{"a":1}'(関数はキーごと削除)

変換ルールの注意点

型/値 シリアライズ結果
undefined, function, Symbol(オブジェクト内) 省略
undefined, function, Symbol(配列内) null
NaN, Infinity, -Infinity "null"
Date ISO文字列(例:"2024-06-10T12:34:56.789Z")※toJSON()実装済み
BigInt TypeError
RegExp, Error "{}"toJSON()未実装なら)
循環参照 TypeError: cyclic structure
プロトタイプや不可視プロパティ 無視(enumerable:false は対象外)
Symbolプロパティ 一切含まない
数値文字列キー(例:"11")は昇順ソートされる {"1":a,"11":b,"foo":c}(非文字列文字列は保持)
// カスタムtoJSONでシリアライズ方法を変更可能
const obj = { id: 1 };
obj.toJSON = () => `CustomID:${this.id}`;
JSON.stringify(obj); // '"CustomID:1"'

Unicodeとエスケープの対応

JSON.stringify({'\u0066': 1}); // '{"f":1}'
JSON.stringify('\n'); // '"\\n"'(制御文字はエスケープされる)

タグ: JSON javascript Serialization parse stringify

6月19日 17:25 投稿