JavaScriptでJSONをパースすることは、Web開発者として何百回もやることになります。 幸い、JavaScriptにはネイティブサポートが組み込まれており——ライブラリは不要です。しかし JSON.parse()JSON.stringify() にはドキュメントが伝えている以上の細かいところがあります。きちんと理解していきましょう。

JSON.parse() — 文字列をオブジェクトに変換する

JSON.parse()はJSON形式の文字列を受け取り、JavaScriptの値に変換します。 その値は通常オブジェクトまたは配列ですが、文字列、数値、真偽値、またはnullになることもあります:

js
const jsonString = '{"name": "Alice", "age": 30, "active": true}';
const user = JSON.parse(jsonString);

console.log(user.name);    // "Alice"
console.log(user.age);     // 30
console.log(user.active);  // true
console.log(typeof user);  // "object"

入力は文字列でなければならないことに注意してください。よくある間違いは、すでにオブジェクトである値をパースしようとすることです——JSON.parse({})はオブジェクトをまず "[object Object]"に変換しますが、これは有効なJSONではないためSyntaxErrorをスローします。

JSON.parse()は必ずtry/catchで囲む

誰も教えてくれないこと:入力が無効なJSONの場合、JSON.parse()SyntaxError をスローします。APIやユーザー入力、ファイルからデータをパースする場合は、必ずそのエラーを処理すべきです。 処理しなければ、1つの不正なレスポンスでアプリがクラッシュする可能性があります:

js
function safeParseJSON(str) {
  try {
    return { data: JSON.parse(str), error: null };
  } catch (err) {
    return { data: null, error: err.message };
  }
}

const { data, error } = safeParseJSON('{"name": "Alice"}');
if (error) {
  console.error('Invalid JSON:', error);
} else {
  console.log(data.name); // "Alice"
}

// Handles bad input gracefully
const result = safeParseJSON('not json at all');
console.log(result.error); // "Unexpected token 'o', "not json "... is not valid JSON"

ほぼすべてのプロジェクトでこのようなラッパーを使っています。エラーハンドリングが一貫し、UIまで バブルアップする見苦しい未捕捉例外を防げます。

JSON.stringify() — オブジェクトを文字列に変換する

JSON.stringify()は逆の処理をします:JavaScriptの値をJSON文字列に変換します。 APIへのデータ送信、localStorageへの保存、ファイルへの書き込み時に使います:

js
const user = {
  name: "Bob",
  age: 25,
  roles: ["admin", "editor"],
  password: "secret123" // we'll handle this later
};

// Basic usage
const jsonString = JSON.stringify(user);
// '{"name":"Bob","age":25,"roles":["admin","editor"],"password":"secret123"}'

// Pretty-printed (great for logs and file output)
const prettyJson = JSON.stringify(user, null, 2);
console.log(prettyJson);
// {
//   "name": "Bob",
//   "age": 25,
//   "roles": ["admin", "editor"],
//   "password": "secret123"
// }

JSON.stringify()の3番目の引数はインデントレベルです。24を使うと読みやすい出力になります。デフォルト(3番目の引数なし)はコンパクトなミニファイされたJSON—— ネットワーク転送に適しています。

stringify()が静かに落とすもの

これは開発者がよくハマるところです。JSON.stringify()はJSONがサポートしていない 特定の値を静かに省略します:

js
const data = {
  name: "Alice",
  greet: function() { return "hi"; },   // Functions → dropped
  undef: undefined,                      // undefined → dropped
  sym: Symbol("key"),                    // Symbols → dropped
  nan: NaN,                              // NaN → null
  inf: Infinity,                         // Infinity → null
  date: new Date("2024-01-15")           // Dates → ISO string
};

console.log(JSON.stringify(data, null, 2));
// {
//   "name": "Alice",
//   "nan": null,
//   "inf": null,
//   "date": "2024-01-15T00:00:00.000Z"
// }
// greet, undef, sym are GONE
注意: undefined値を持つオブジェクトをstringifyしてからパースすると、 それらのキーは完全に消えています。後でコードがobj.key === undefinedをチェックする場合、 これが微妙なバグを引き起こす可能性があります。

APIからJSONを取得する — 実際のパターン

実際には、JSONのパースのほとんどはAPIからデータを取得する際に発生します。 Fetch API はこれをシンプルにしてくれます——response.json()がパースを自動的に処理します:

js
async function getUser(userId) {
  try {
    const response = await fetch(`https://api.example.com/users/${userId}`);

    if (!response.ok) {
      throw new Error(`HTTP error: ${response.status}`);
    }

    const user = await response.json(); // parses JSON automatically
    return user;
  } catch (err) {
    console.error('Failed to fetch user:', err);
    return null;
  }
}

const user = await getUser(42);
if (user) {
  console.log(`Hello, ${user.name}!`);
}

response.json()は本質的にJSON.parse(await response.text())です。 レスポンスボディが有効なJSONでない場合もエラーをスローするため、本番コードでは別途キャッチする価値があります。

ネストされたJSONデータを扱う

深くネストされたJSONは実際のAPIでよく見られます。存在しないキーでクラッシュせずに安全にナビゲートする方法はこちらです:

js
const apiResponse = {
  "user": {
    "profile": {
      "address": {
        "city": "Berlin"
      }
    }
  }
};

// Unsafe — throws if any level is undefined
const city1 = apiResponse.user.profile.address.city; // "Berlin"

// Safe with optional chaining (ES2020+) — returns undefined instead of throwing
const city2 = apiResponse?.user?.profile?.address?.city; // "Berlin"
const zip   = apiResponse?.user?.profile?.address?.zip;  // undefined (not a crash)

// With a fallback using nullish coalescing
const country = apiResponse?.user?.profile?.address?.country ?? "Unknown";

オプショナルチェーン?.)と Null合体演算子??)は、 2020年以降ECMAScript仕様に含まれるモダンなJavaScriptの機能で、 不確かなJSON構造を扱う際の安全性を大幅に高めます。積極的に使いましょう——すべてのモダンブラウザとNode.js 14+でサポートされています。

replacer関数とreviver関数

JSON.stringify()JSON.parse()はどちらも、変換をカスタマイズできる 第2引数を受け取ります。実際のシナリオで本当に役立ちます:

js
// replacer: filter or transform values during stringify
const user = { name: "Alice", password: "s3cr3t", age: 30 };
const safe = JSON.stringify(user, ["name", "age"]); // only include these keys
// '{"name":"Alice","age":30}'  — password is excluded

// reviver: transform values during parse
const dateJson = '{"name": "Alice", "createdAt": "2024-01-15T09:30:00Z"}';
const parsed = JSON.parse(dateJson, (key, value) => {
  if (key === "createdAt") return new Date(value); // convert string to Date
  return value;
});
console.log(parsed.createdAt instanceof Date); // true
console.log(parsed.createdAt.getFullYear());   // 2024

reviverパターンは特に、JSONにはネイティブの日付型がないため、JSONを経由したラウンドトリップ後に Dateオブジェクトを復元する際に役立ちます。

便利なツール

JavaScriptプロジェクトでJSONを扱う際、これらのツールが時間を節約してくれます: ミニファイされたAPIレスポンスを整形するJSONフォーマッター、 構文エラーをチェックするJSONバリデーター、 大きなペイロードから特定のフィールドをクエリするJSON Path、 そしてJSONを文字列に埋め込む必要があるときのJSON Escape。 より深く学びたい場合は、 MDNのJSONリファレンス が優れています。

まとめ

JSON.parse()JSON.stringify()が必要な2つの関数です。 身につけるべき重要な習慣:parse()は常にtry/catchで囲む、ネストされたアクセスにはオプショナルチェーンを使う、 そしてstringify()が静かに落とすものを把握する。この3つを正しくできれば、 実際のJSONシナリオの99%を驚きなくこなせるようになります。