JavaScript における `JSON` クラス

JavaScript における `JSON` クラス

この記事ではJavaScript における JSON クラスについて説明します。

JavaScript における JSON クラスについて実際的なサンプルを含めて解説します。

YouTube Video

JavaScript における JSON クラス

JSON オブジェクトは主に 2 つのメソッドを持ちます。JSON.stringify()は、オブジェクトをJSON文字列に変換し、JSON.parse()は、JSON 文字列からオブジェクトを作成します。JSON はデータ交換フォーマットで、JavaScript の値のうち一部のみを表現できます。関数や undefinedSymbol、循環参照はそのままではJSONにできません。

JSON.stringify()

まずは、オブジェクトをJSON文字列に変換する基本例を示します。

1// Convert a JavaScript object to a JSON string.
2const obj = { name: "Alice", age: 30, active: true };
3const jsonString = JSON.stringify(obj);
4console.log(jsonString); // {"name":"Alice","age":30,"active":true}
  • このコードは普通のオブジェクトをJSON文字列に変換する最も基本的な例です。JSON.stringify は同期的に文字列を返します。

JSON.stringify の第 2 引数(replacer)

JSON.stringify の第2引数であるreplacerを指定すると、変換ルールを細かく制御できます。ここでは配列として指定して特定のプロパティだけ残す例を示します。

1// Use a replacer array to include only specific properties during stringification.
2const user = { id: 1, name: "Bob", password: "secret", role: "admin" };
3const safeJson = JSON.stringify(user, ["id", "name", "role"]);
4console.log(safeJson); // {"id":1,"name":"Bob","role":"admin"}
  • この例では password を除外して安全にログ出力できる形にしています。セキュリティ上の理由で機密情報を取り除きたいときに便利です。

replacer を関数で使う(値を変換・フィルタリング)

より柔軟にしたい場合、関数を渡してキーと値ごとに処理できます。以下は Date を ISO 文字列で保持する例です。

 1// Use a replacer function to customize serialization.
 2const data = { name: "Carol", joined: new Date("2020-01-01T12:00:00Z") };
 3const jsonCustom = JSON.stringify(data, (key, value) => {
 4  // Convert Date objects to ISO strings explicitly
 5  if (this && this[key] instanceof Date) {
 6    return this[key].toISOString();
 7  }
 8  return value;
 9});
10console.log(jsonCustom); // {"name":"Carol","joined":"2020-01-01T12:00:00.000Z"}
  • 関数型の replacer では、キー名と値が渡され、戻した値が最終JSONに使われます。this は親オブジェクトを指すのでネストされた変換にも使えます。

JSON.parse() の基本

JSON.parseJSON文字列をオブジェクトに戻します。try/catch でラップして不正なJSONにも対処できるようにします。

1// Parse a JSON string into an object and handle parsing errors.
2const jsonText = '{"title":"Example","count":42}';
3try {
4  const parsed = JSON.parse(jsonText);
5  console.log(parsed.title); // Example
6} catch (err) {
7  console.error("Invalid JSON:", err.message);
8}
  • 文字列が不正だと例外が投げられるので、外部ソースを扱う際は常に例外処理を入れるようにします。

reviver を使ったカスタム復元(Date を復元する例)

JSON.parse の第2引数 reviver を使うと値の復元処理を行えます。ここでは文字列として保存した日付を Date に戻す例を示します。

 1// Use a reviver to turn ISO date strings back into Date objects.
 2const jsonWithDate = '{"name":"Dana","joined":"2021-06-15T09:00:00.000Z"}';
 3const obj = JSON.parse(jsonWithDate, (key, value) => {
 4  // A simple check for ISO date-like strings
 5  if (typeof value === "string" && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/.test(value)) {
 6    return new Date(value);
 7  }
 8  return value;
 9});
10console.log(obj.joined instanceof Date); // true
  • reviver はキーごとに呼ばれ、戻した値が最終オブジェクトに採用されます。日付の形式チェックはアプリケーションに合わせて厳密に行ってください。

整形(pretty-print)と space 引数

可読性のためにインデントを付けて出力するには JSON.stringify の第3引数に数値か文字列を渡します。

1// Pretty-print an object with 2-space indentation for readability.
2const config = { host: "localhost", port: 3000, debug: true };
3const pretty = JSON.stringify(config, null, 2);
4console.log(pretty);
5/* {
6  "host": "localhost",
7  "port": 3000,
8  "debug": true
9} */
  • ログや設定ファイルの出力など、人が読む目的のJSONにはインデントを指定すると便利です。ただしサイズが増えることに注意する必要があります。

循環参照(circular reference)を扱う方法

JSON.stringify は循環参照があると TypeError を投げます。一般的な対処法は seen セットを使って循環を避ける replacer を作ることです。

 1// Safely stringify objects that may contain circular references.
 2function safeStringify(value) {
 3  const seen = new WeakSet();
 4  return JSON.stringify(value, (key, val) => {
 5    if (val && typeof val === "object") {
 6      if (seen.has(val)) return "[Circular]";
 7      seen.add(val);
 8    }
 9    return val;
10  });
11}
12
13const a = { name: "A" };
14a.self = a;
15console.log(safeStringify(a)); // {"name":"A","self":"[Circular]"}
  • WeakSet を使うことでメモリリークを避けつつ循環参照を検出できます。循環参照の場合には、"[Circular]"を返しています。"[Circular]"の代わりに参照IDを付与するなど応用も可能です。

toJSON メソッドによるカスタムシリアライズ

オブジェクトに toJSON メソッドを定義すると、JSON.stringify はそちらの戻り値を使います。型ごとに変換ルールを埋め込む場合に便利です。

 1// Define toJSON on a class to customize its JSON representation.
 2class Point {
 3  constructor(x, y) {
 4    this.x = x;
 5    this.y = y;
 6  }
 7  toJSON() {
 8    // This will be used by JSON.stringify
 9    return { x: this.x, y: this.y, type: "Point" };
10  }
11}
12
13const p = new Point(10, 20);
14console.log(JSON.stringify(p)); // {"x":10,"y":20,"type":"Point"}
  • toJSON はオブジェクトレベルでのシリアライズルールを定義でき、replacer よりも局所的で直感的な扱いができます。

JSONの制限と注意点

JSONにできない代表例は、undefined、関数、SymbolBigInt、および循環参照などです。数値の精度(大きな整数や IEEE 浮動小数点の丸め)にも注意が必要です。

 1// Demonstrate values that can't be represented in JSON.
 2const sample = {
 3  a: undefined,
 4  b: function () {},
 5  c: Symbol("s"),
 6  d: 9007199254740993n // BigInt (note: JSON.stringify will throw on BigInt)
 7};
 8// JSON.stringify will throw for BigInt and will drop undefined, functions, symbols in objects.
 9try {
10  console.log(JSON.stringify(sample));
11} catch (err) {
12  console.error("Error during stringify:", err.message);
13}
  • BigInt を扱う場合は文字列に変換するか、カスタムの replacertoJSON で表現方法を定義するようにします。

セキュリティ(信頼できないJSONを扱うとき)

JSON.parse 自体は安全で、eval より安全とされていますが、パース後に得たオブジェクトを信頼して直接プロパティ参照や DB クエリに使うのは危険です。常にバリデーションを行い、期待するスキーマに沿っているか検証する必要があります。

1// Parse external JSON and validate expected properties before use.
2const external = '{"username":"eve","role":"user"}';
3const parsed = JSON.parse(external);
4if (typeof parsed.username === "string" && ["user","admin"].includes(parsed.role)) {
5  console.log("Safe to use parsed.username and parsed.role.");
6} else {
7  throw new Error("Invalid payload");
8}
  • 入力検証(スキーマバリデーション)を行うには ajv などのライブラリを使うと堅牢です。

パフォーマンス上の考慮

大きなオブジェクトを頻繁にシリアライズ/デシリアライズする場合、CPU とメモリに負荷がかかります。必要であれば差分のみを送る、バイナリ(MessagePack)を使う、またはストリーミング(逐次処理)などの方法を検討できます。ブラウザ環境では structuredClone(コピー)や postMessage の転送可能オブジェクトを活用する場面もあります。

具体的なアドバイス(短く)

次のような点も考慮できます。

  • ログ用には pretty-print を避けて一行JSONにし、サイズを削減します。
  • 頻繁にシリアライズするオブジェクトは、不要なプロパティを削除するなど事前に軽量化します。
  • replacer で走査を最小化して、不要なプロパティを除外します。

実践的な例:API リクエストの送受信フロー

最後に、サーバーに送るためにオブジェクトを安全にJSON化し、レスポンスを受け取って date を復元する一連の流れを示します。

 1// Prepare payload, stringify safely, send via fetch, and revive dates on response.
 2async function sendUserUpdate(url, user) {
 3  // Remove sensitive info before sending
 4  const payload = JSON.stringify(user, (k, v) => (k === "password" ? undefined : v));
 5  const res = await fetch(url, {
 6    method: "POST",
 7    headers: { "Content-Type": "application/json" },
 8    body: payload
 9  });
10
11  const text = await res.text();
12  // Reviver: convert ISO date strings back to Date
13  const data = JSON.parse(text, (key, value) => {
14    if (typeof value === "string" && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/.test(value)) {
15      return new Date(value);
16    }
17    return value;
18  });
19  return data;
20}
  • この例では、送信前に password を除外し、受信後は reviver で日付を復元しています。実際の運用ではエラーハンドリングやタイムアウト処理も追加する必要があります。

まとめ

JSON.stringify()JSON.parse() を使えば、オブジェクトと文字列の相互変換ができます。replacerreviver を使うことで変換処理をカスタマイズでき、クラスに toJSON を実装すればオブジェクトごとの制御も可能です。循環参照や BigInt、関数などはそのままでは扱えないため、事前に処理しておく必要があります。

外部からの入力は必ず検証し、機密情報はログや通信に含めないようにしましょう。データ量が大きい場合は、整形出力を避けたり、差分送信やバイナリ形式の利用も検討すると効率的です。

YouTubeチャンネルでは、Visual Studio Codeを用いて上記の記事を見ながら確認できます。 ぜひYouTubeチャンネルもご覧ください。

YouTube Video