ウェブエンジニア問題集

JSONの基礎と実践 — stringify・parseの引数、ディープコピー、よくある落とし穴

APIとのデータのやり取り、localStorage への保存、設定ファイルの読み書き——これらはすべて JSON という形式を通じて行われます。JavaScriptには JSON.stringifyJSON.parse という2つのメソッドが用意されており、この2つさえ押さえれば日常の大半は対応できます。

この章では、その2メソッドの引数を表で整理し、ディープコピーへの応用、そして「Date が文字列になる」「undefined が消える」といったハマりやすい落とし穴までを実務目線で見ていきます。

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

JSON(JavaScript Object Notation)は、データを文字列で表現するためのフォーマットです。見た目はJavaScriptのオブジェクトによく似ていますが、別物です。

// JavaScriptのオブジェクト(コード)
const user = { name: '太郎', age: 25 };
 
// JSON文字列(あくまで「文字列」)
const json = '{"name":"太郎","age":25}';
データを変換してやり取りするイメージ

JavaScriptオブジェクトとの主な違いは次のとおりです。

観点JavaScriptオブジェクトJSON
正体プログラム上の値文字列
キーの引用符省略可(name:必ずダブルクオート"name":
値に使えるもの関数・undefined なども可文字列・数値・真偽値・null・配列・オブジェクトのみ
末尾カンマ許される不可

JSON.stringify — オブジェクト → JSON文字列

JavaScriptの値をJSON文字列に変換します。APIへの送信や localStorage への保存で使います。

構文: JSON.stringify(value, replacer?, space?)

引数説明
value変換したい値(オブジェクト・配列・数値など)
replacer(省略可)出力をフィルタ・加工する関数、または残したいキー名の配列
space(省略可)インデント。数値(スペース数)や '\t' を渡すと整形して読みやすくなる

戻り値: JSON文字列

const user = { name: '太郎', age: 25, roles: ['admin'] };
 
JSON.stringify(user);
// '{"name":"太郎","age":25,"roles":["admin"]}'
 
// 第3引数でインデント(人間が読む・ログ出力に便利)
JSON.stringify(user, null, 2);
// {
//   "name": "太郎",
//   "age": 25,
//   "roles": ["admin"]
// }
 
// 第2引数で特定のキーだけ残す
JSON.stringify(user, ['name', 'age']);
// '{"name":"太郎","age":25}'(rolesは除外)
学習者学習者

第2引数の null って何…?インデントしたいだけなのに、なんで null を挟むの?

space(インデント)は第3引数なので、間の replacer(第2引数)を使わないときは null で飛ばす必要があるのです。「フィルタは要らないけど整形はしたい」→ JSON.stringify(value, null, 2) という頻出パターンとして覚えてしまいましょう。

JSON.parse — JSON文字列 → オブジェクト

JSON文字列をJavaScriptの値に戻します。APIのレスポンスや localStorage から読み込んだデータを使えるようにします。

構文: JSON.parse(text, reviver?)

引数説明
textパースするJSON文字列
reviver(省略可)各値を変換しながら復元する関数(日付文字列を Date に戻す等)

戻り値: 文字列を解釈したJavaScriptの値(オブジェクト・配列など)

const json = '{"name":"太郎","age":25}';
const user = JSON.parse(json);
 
user.name; // "太郎"(オブジェクトとして使える)
user.age; // 25

parse は必ず try/catch で囲む

JSON.parse は、不正な文字列を渡すと例外を投げます。APIのレスポンスや外部から来た文字列は壊れている可能性があるため、try/catch で囲むのが鉄則です。

function safeParse(text) {
  try {
    return JSON.parse(text);
  } catch {
    return null; // パース失敗時の既定値
  }
}
 
safeParse('{"ok":true}'); // { ok: true }
safeParse('壊れた文字列'); // null(例外を握りつぶして既定値)
先生先生

stringify は基本失敗しないけど、parse は外から来た文字列を相手にするぶん失敗しやすい。だから parse には try/catch をセットで、と覚えておこう。

ディープコピーに使う

JSON.stringifyJSON.parse を通すと、ネストしたオブジェクトを丸ごと複製(ディープコピー)できます。ただし現在は専用の structuredClone が使えるため、まずはそちらが第一候補です。

方法特徴
structuredClone(obj)推奨DateMapSet などもそのままコピーでき、循環参照もOK
JSON.parse(JSON.stringify(obj))手軽だが、Date が文字列化・undefined/関数が消えるなどJSONの制約を受ける
スプレッド { ...obj }浅いコピー。ネストした中身は共有されたまま(ディープコピーではない)
const original = { user: { name: '太郎' }, tags: ['a', 'b'] };
 
// ⭕ 推奨:構造をそのまま複製
const copy = structuredClone(original);
copy.user.name = '花子';
original.user.name; // "太郎"(元は影響を受けない)
学習者学習者

スプレッド構文 { ...obj } でコピーしたはずなのに、ネストした中身を書き換えたら元まで変わっちゃった…!

スプレッドは1階層だけのコピー(浅いコピー)です。ネストしたオブジェクトは参照が共有されたままなので、深い階層まで独立させたいときは structuredClone を使います。浅いコピー/スプレッドの詳細は分割代入とスプレッド構文を参照してください。

よくある落とし穴 — JSONで「消える・変わる」値

JSONが表現できるのは「文字列・数値・真偽値・null・配列・オブジェクト」だけです。それ以外の値は JSON.stringify消えたり変換されたりします。

const data = {
  date: new Date(2026, 5, 6), // Date
  fn: () => {}, // 関数
  notDefined: undefined, // undefined
  big: 100n, // BigInt
  num: NaN, // NaN
};
 
JSON.stringify(data);
// '{"date":"2026-06-05T15:00:00.000Z","num":null}'

何が起きたかを整理すると次のとおりです。

元の値JSON化の結果
Date文字列になる(parse で戻しても Date には戻らない)
関数 / undefined / Symbolキーごと消える
NaN / Infinitynull になる
BigInt例外を投げる(変換不可)
循環参照(自分自身を含む)例外を投げる
// 循環参照はエラーになる
const a = {};
a.self = a;
JSON.stringify(a); // TypeError: Converting circular structure to JSON

早見表

やりたいこと使うもの
オブジェクト → JSON文字列JSON.stringify(obj)
読みやすく整形して出力JSON.stringify(obj, null, 2)
JSON文字列 → オブジェクトJSON.parse(text)try/catch 必須)
安全にパースtry { JSON.parse(text) } catch { ... }
ディープコピーstructuredClone(obj)(推奨)
浅いコピー{ ...obj }(ネストは共有される点に注意)

よくあるハマりどころ

JSON処理のハマりどころを確認するイメージ

1. parse を try/catch せずにクラッシュ

外部から来た文字列をそのまま JSON.parse すると、壊れたデータで例外が飛びアプリが落ちます。必ず try/catch(または安全ラッパー)を通します。

2. インデントしようとして引数を間違える

整形は第3引数JSON.stringify(obj, 2) ではなく JSON.stringify(obj, null, 2) です(第2引数 replacernull で飛ばす)。

3. Date が文字列になって戻らない

stringifyDate はISO文字列化されます。parse してもただの文字列のまま。日付を保持したいなら reviverstructuredClone

4. undefined のプロパティが消える

{ a: 1, b: undefined }stringify すると '{"a":1}' になります。「キーがあるはず」と思い込むとバグります。

ちゃんと使うためのポイント

  • JSONは文字列。JavaScriptオブジェクトとは別物で、キーは必ずダブルクオート
  • JSON.stringify(value, replacer?, space?) — 整形したいだけなら JSON.stringify(obj, null, 2)
  • JSON.parse(text) は壊れた入力で例外を投げる。try/catch をセットで
  • ディープコピーは structuredClone が第一候補。JSON 経由はJSONの制約を受ける
  • Date・関数・undefinedBigInt・循環参照は、JSON化で消える/変わる/エラーになる

この章で、配列・文字列・数値・日付・JSONといったよく使う組み込みオブジェクトを一通り押さえました。データを「取得し(fetch)→ パースし → 加工して → 表示する」という一連の流れは、非同期処理(Promise)DOM操作とつながっていきます。リファレンスとして、必要なときにこの章へ戻ってきてください。