2.

JavaScript で配列を JSON に変換|JSON.stringify の落とし穴

編集
この記事の要点
  • 変換は JSON.stringify(配列)。第 3 引数に数値を渡すとインデント付きになる
  • undefined・関数・Symbol は消える。配列の中では null に置き換わる
  • DateISO 8601 の文字列になる。戻すときは自分で new Date() する
  • Map Set{} になる。先に配列へ変換してから渡す
  • 循環参照があると TypeErrorBigInt も変換できない

基本

const items = [
  { id: 1, name: "りんご", price: 120 },
  { id: 2, name: "みかん", price: 80 },
];

console.log(JSON.stringify(items));
// [{"id":1,"name":"りんご","price":120},{"id":2,"name":"みかん","price":80}]

console.log(JSON.stringify(items, null, 2));
// [
//   {
//     "id": 1,
//     "name": "りんご",
//     "price": 120
//   },
//   ...
// ]

console.log(JSON.stringify([1, "a", true, null]));   // [1,"a",true,null]
console.log(JSON.stringify([]));                      // []

日本語はエスケープされず、そのまま出力されます。PHP の json_encode() と違い JSON_UNESCAPED_UNICODE のような指定は不要です。

消える値・変わる値

const arr = [1, undefined, () => {}, Symbol("s"), NaN, Infinity, new Date()];
console.log(JSON.stringify(arr));
// [1,null,null,null,null,null,"2026-09-07T03:00:00.000Z"]

const obj = { a: 1, b: undefined, c: () => {} };
console.log(JSON.stringify(obj));
// {"a":1}       ← オブジェクトの場合はキーごと消える
配列の中オブジェクトの値
undefined / 関数 / Symbolnull になるキーごと消える
NaN / Infinitynull になる
DateISO 8601 の文字列になる
Map / Set{} になる(中身が消える)
BigIntTypeError
疎な配列の穴null になる

「送ったはずのプロパティが相手に届かない」原因の大半は undefined です。値が無いことを伝えたいなら null を明示的に入れてください。

Map・Set を変換する

const m = new Map([["a", 1], ["b", 2]]);
const s = new Set([1, 2, 3]);

console.log(JSON.stringify(m));                    // {}   中身が消える
console.log(JSON.stringify([...m]));               // [["a",1],["b",2]]
console.log(JSON.stringify(Object.fromEntries(m))); // {"a":1,"b":2}
console.log(JSON.stringify([...s]));               // [1,2,3]

循環参照

const a = { name: "A" };
const b = { name: "B", parent: a };
a.child = b;                     // 互いを参照している

JSON.stringify(a);
// TypeError: Converting circular structure to JSON

// 同じものを 2 回目以降は無視する
const seen = new WeakSet();
JSON.stringify(a, (key, value) => {
  if (typeof value === "object" && value !== null) {
    if (seen.has(value)) return "[Circular]";
    seen.add(value);
  }
  return value;
});

DOM 要素や event オブジェクトを含む配列はほぼ確実に循環参照になります。必要な値だけを取り出してから変換してください。

出力する項目を選ぶ

const users = [
  { id: 1, name: "田中", password: "secret", token: "xxx" },
];

// 第 2 引数に配列を渡すとそのキーだけになる
console.log(JSON.stringify(users, ["id", "name"]));
// [{"id":1,"name":"田中"}]

// 関数を渡すと値ごとに加工できる
console.log(JSON.stringify(users, (key, value) => {
  if (key === "password" || key === "token") return undefined;   // 消す
  return value;
}));
// [{"id":1,"name":"田中"}]

// 事前に整形するほうが分かりやすい
const safe = users.map(({ id, name }) => ({ id, name }));
console.log(JSON.stringify(safe));

パスワードやトークンを含むオブジェクトをそのまま stringify しないでください。ログや localStorage に平文で残ります。

クラスに toJSON を持たせる

class User {
  constructor(id, name, password) {
    this.id = id;
    this.name = name;
    this.password = password;
  }

  toJSON() {                     // stringify から自動的に呼ばれる
    return { id: this.id, name: this.name };
  }
}

console.log(JSON.stringify([new User(1, "田中", "secret")]));
// [{"id":1,"name":"田中"}]

Date が文字列になるのも、Date.prototype.toJSON() が定義されているからです。

戻すとき(parse)

const json = '[{"id":1,"at":"2026-09-07T03:00:00.000Z"}]';

const items = JSON.parse(json);
console.log(typeof items[0].at);        // string   ← Date には戻らない

// 復元関数(reviver)で型を戻す
const items2 = JSON.parse(json, (key, value) =>
  key === "at" ? new Date(value) : value);
console.log(items2[0].at instanceof Date);   // true

// 壊れた JSON は例外になる
try {
  JSON.parse("{bad}");
} catch (e) {
  console.error(e.message);   // Unexpected token b in JSON at position 1
}

JSON.parse()必ず try...catch で囲んでください。API が HTML のエラーページを返したときなど、想定外の入力で必ず例外になります。

送信・保存で使う

// サーバーへ送る
await fetch("/api/items", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify(items),      // body には文字列を渡す
});

// localStorage は文字列しか保存できない
localStorage.setItem("items", JSON.stringify(items));
const restored = JSON.parse(localStorage.getItem("items") ?? "[]");

// ファイルとしてダウンロードさせる
const blob = new Blob([JSON.stringify(items, null, 2)],
  { type: "application/json" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "items.json";
a.click();
URL.revokeObjectURL(url);

深いコピーに使うときの注意

const original = [{ at: new Date(), fn: () => {}, n: undefined }];

const copy1 = JSON.parse(JSON.stringify(original));
// at は文字列に、fn と n は消える

const copy2 = structuredClone(original);
// TypeError: 関数は複製できない

const copy3 = structuredClone([{ at: new Date(), n: 1 }]);
// Date は Date のまま複製される(こちらが正しい深いコピー)

深いコピーの目的なら structuredClone() を使ってください。JSON 経由のコピーは型が失われます。

大きな配列を扱う

// 1 行 1 レコードの JSON Lines なら、行ごとに処理できる
const jsonl = items.map((o) => JSON.stringify(o)).join("\n");

// 読み込む側
for (const line of jsonl.split("\n")) {
  if (!line) continue;
  const item = JSON.parse(line);
}

// 分割して送る
const CHUNK = 500;
for (let i = 0; i < items.length; i += CHUNK) {
  await fetch("/api/items", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(items.slice(i, i + CHUNK)),
  });
}

JSON.stringify()結果の文字列をすべてメモリに載せます。数十 MB を超えるならレコード単位に分けるか、サーバー側で生成してください。

整形と最小化

const data = [{ a: 1, b: [2, 3] }];

JSON.stringify(data);              // 最小(送信時はこれ)
JSON.stringify(data, null, 2);     // スペース 2 個でインデント
JSON.stringify(data, null, "\t");  // タブでインデント

// 整形しなおす(受け取った JSON を読みやすくする)
const pretty = JSON.stringify(JSON.parse(raw), null, 2);

// キーの順序を固定する(差分を取りやすくする)
const sorted = JSON.stringify(data, (k, v) =>
  v && typeof v === "object" && !Array.isArray(v)
    ? Object.fromEntries(Object.entries(v).sort())
    : v, 2);

通信では整形しないのが原則です。インデントのぶんだけ転送量が増えます。整形が要るのは、人が読むファイルやログに出すときだけです。

比較に使うときの注意

const a = { x: 1, y: 2 };
const b = { y: 2, x: 1 };

console.log(JSON.stringify(a) === JSON.stringify(b));   // false   キーの順序が違う

// 配列は順序に意味があるので比較に使える
console.log(JSON.stringify([1, 2]) === JSON.stringify([1, 2]));   // true

オブジェクトの等価判定に JSON.stringify() を使わないでください。プロパティを追加した順序で結果が変わります。undefined のキーが消える点も含め、判定用の関数を別に用意するのが確実です。

PHP・Python との違い

JavaScript他言語でよくある挙動
日本語そのまま出るPHP は既定で \uXXXX にエスケープする
空の連想配列{}[] が明確に別PHP は空配列が [] になる
穴あき配列null で埋まるPHP はキー付きオブジェクトになる
失敗したとき例外を投げるPHP の json_encodefalse を返す

とくに「配列のつもりがオブジェクトになる」問題は、PHP 側で array_values() を通さずに json_encode() したときに起きます。JavaScript 側で Array.isArray()false になったら、送り元を疑ってください。

関連

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. JSONから配列に変換
  2. 配列からJSONに変換