JavaScript で JSON をフォーマットする方法 — 5 つのメソッド
JSON はあらゆる場所にあります: API レスポンス、設定ファイル、データベースエクスポート、ログパイプライン。しかし生の JSON 出力はしばしばコンパクトな塊です — 改行なし、インデントなし、シリアライザーが吐き出した順序のキー。これがデバッグを苦痛にし、コラボレーションを必要以上に難しくします。
JavaScript は、組み込みの JSON.stringify から、メモリにロードできない大きすぎるファイルのためのストリーミングライブラリまで、JSON をフォーマットするいくつかの方法を提供します。このガイドでは、最も一般的なケースと開発者を不意打ちするエッジケースをカバーする 5 つの実用的なメソッドを説明します。
メソッド 1: インデント付き JSON.stringify
最もシンプルなアプローチは、すでに言語に組み込まれています。JSON.stringify は 3 つの引数を受け入れます: シリアル化する値、replacer (メソッド 2 で詳しく説明)、そしてインデントを制御する space パラメーターです。
const data = {
name: "Alice",
role: "engineer",
skills: ["JavaScript", "TypeScript", "Node.js"],
active: true,
};
// 2 スペースインデント (一般的なデフォルト)
console.log(JSON.stringify(data, null, 2));
// 4 スペースインデント
console.log(JSON.stringify(data, null, 4));
// タブインデント
console.log(JSON.stringify(data, null, "\t")); null, 2 を使うと、出力は次のようになります:
{
"name": "Alice",
"role": "engineer",
"skills": [
"JavaScript",
"TypeScript",
"Node.js"
],
"active": true
}
2 スペースは JavaScript プロジェクトで最も一般的な慣習です — ESLint、Prettier、ほとんどのスタイルガイドのデフォルトと一致します。4 スペースは Python 隣接ツールで一般的です。space が省略されるか 0 に設定されると、出力はコンパクトな 1 行の文字列になります — ワイヤー送信に理想的です。
ブラウザで即座に JSON をフォーマットするには、JSON フォーマッタツールも使えます — 生の JSON を貼り付け、整形された出力を取得し、コードを書かずに結果をコピーできます。
メソッド 2: カスタム replacer 関数
JSON.stringify の 2 番目の引数 — replacer — は、シリアル化の前に値をフィルタリングおよび変換する関数または配列です。これは、出力に何が入るかを実際に制御できる場所です。
配列形式は、キーホワイトリストのための最もシンプルなアプローチです:
const user = {
id: 42,
name: "Bob",
password: "s3cr3t",
email: "bob@example.com",
createdAt: new Date(),
};
// 配列の replacer: リストされたキーのみを含める
const safeJson = JSON.stringify(user, ["id", "name", "email"], 2);
console.log(safeJson);
// {
// "id": 42,
// "name": "Bob",
// "email": "bob@example.com"
// }
関数形式は、値ごとの制御を提供します。値を含めるには値を返し、削除するには undefined を返し、または変換された値を返します:
function replacer(key, value) {
// アンダースコアで始まるキーを削除 (プライベート慣習)
if (key.startsWith("_")) return undefined;
// 機密フィールドをマスク
if (key === "password" || key === "token") return "[REDACTED]";
// Date オブジェクトを明示的に ISO 文字列に変換
if (value instanceof Date) return value.toISOString();
return value;
}
const payload = {
id: 1,
name: "Carol",
password: "hunter2",
_internalFlag: true,
lastLogin: new Date("2026-05-01"),
};
console.log(JSON.stringify(payload, replacer, 2));
// {
// "id": 1,
// "name": "Carol",
// "password": "[REDACTED]",
// "lastLogin": "2026-05-01T00:00:00.000Z"
// } replacer はネストされたすべてのオブジェクトと配列に対して再帰的に実行されます。このパターンは、ディスクに書き込んだり可観測性サービスに送信したりする前に資格情報を取り除きたいロギングパイプラインに有用です。
メソッド 3: ソートされたキーで整形する
JavaScript オブジェクトはキー順序を保証しません (V8 とほとんどのエンジンは文字列キーの挿入順を保持しますが)。決定論的な出力が必要なとき — 差分、キャッシング、または正規表現のため — キーをアルファベット順にソートすることが正しい方法です。
function sortedStringify(value, indent = 2) {
return JSON.stringify(value, sortReplacer, indent);
}
function sortReplacer(key, value) {
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
return Object.keys(value)
.sort()
.reduce((sorted, k) => {
sorted[k] = value[k];
return sorted;
}, {});
}
return value;
}
const config = {
version: "1.0",
author: "Dave",
dependencies: { typescript: "^5.4", eslint: "^9.0", astro: "^5.0" },
name: "my-project",
};
console.log(sortedStringify(config));
// {
// "author": "Dave",
// "dependencies": { "astro": "^5.0", "eslint": "^9.0", "typescript": "^5.4" },
// "name": "my-project",
// "version": "1.0"
// }
ソートされたキーは、異なるシリアライザーからの任意の並べ替えではなく、実際に変更された行のみを git 差分が示すことを意味します。これは、バージョン管理にチェックインされた package.json や類似の設定ファイルに特に有用です。
JSON を他の形式に変換するには、JSON to YAML ツールと JSON to CSV ツールもその出力でキー順を処理します。
メソッド 4: 文字列からフォーマット (Parse + Stringify)
実世界の JSON は通常、文字列として到着します — fetch レスポンス、ファイル読み取り、クリップボード貼り付け、またはデータベース TEXT 列から。最初に解析し、その後再フォーマットする必要があります。重要なのは適切なエラー処理です: 無効な JSON はスローし、それを優雅に捕捉したいのです。
function formatJsonString(rawString, indent = 2) {
try {
const parsed = JSON.parse(rawString);
return { ok: true, result: JSON.stringify(parsed, null, indent) };
} catch (err) {
return { ok: false, error: err.message };
}
}
const raw = '{"name":"Eve","scores":[100,95,88],"active":true}';
const { ok, result, error } = formatJsonString(raw);
if (ok) {
console.log(result);
// {
// "name": "Eve",
// "scores": [100, 95, 88],
// "active": true
// }
} else {
console.error("解析に失敗しました:", error);
}
// 無効な入力
const bad = '{"name": "Eve", "broken":}';
const r2 = formatJsonString(bad);
// { ok: false, error: "Unexpected token '}'" } 解析エラーを構造化された戻り値オブジェクトでラップすると、この関数をすべての呼び出しサイトを try/catch で囲まずに UI コンポーネントとビルドスクリプトで安全に使用できます。MDN の JSON.stringify ドキュメントは完全なパラメーター仕様をカバーし、RFC 8259 はプロトコルレベルで有効な JSON が何かを定義します。
メソッド 5: 大きなファイルのストリーミング
メソッド 1〜4 はすべて、フォーマットの前に JSON 構造全体をメモリにロードします。数百メガバイトから数ギガバイトの範囲のファイルでは、これは Node.js イベントループをブロックし、プロセス全体をクラッシュさせる可能性があります。
ストリーミングアプローチはファイルをチャンク単位で読み取り、フォーマットされた出力を段階的に書き込みます。NDJSON (1 行に 1 つの JSON オブジェクト、ログファイルやデータベースエクスポートで一般的) には、追加の依存関係なしで readline ベースのアプローチが機能します:
import { createReadStream, createWriteStream } from "node:fs";
import { createInterface } from "node:readline";
async function formatNdjsonFile(inputPath, outputPath) {
const rl = createInterface({
input: createReadStream(inputPath),
crlfDelay: Infinity,
});
const output = createWriteStream(outputPath);
for await (const line of rl) {
if (!line.trim()) continue;
try {
const obj = JSON.parse(line);
output.write(JSON.stringify(obj, null, 2) + "\n---\n");
} catch (e) {
output.write("[無効な JSON 行: " + e.message + "]\n---\n");
}
}
output.end();
} NDJSON は最もシンプルなストリーミング形式です: 各行が有効で完全な JSON オブジェクトです。多くのエクスポートツールがそれを正にサポートするのは、簡単にストリーム可能だからです。大きなデータエクスポートの形式を制御する場合、単一の巨大な JSON 配列よりも NDJSON を優先してください。
注意すべきエッジケース
これらは標準的な JSON フォーマットが静かに失敗したり、予期せずスローしたりするシナリオです。
循環参照
JSON.stringify は、オブジェクトが直接または間接的に自身を参照する場合、TypeError をスローします。WeakSet で訪問したオブジェクトを追跡する replacer で修正します:
function safeStringify(obj, indent = 2) {
const seen = new WeakSet();
return JSON.stringify(obj, (key, value) => {
if (typeof value === "object" && value !== null) {
if (seen.has(value)) return "[Circular]";
seen.add(value);
}
return value;
}, indent);
}
const a = { name: "circular" };
a.self = a;
console.log(safeStringify(a));
// { "name": "circular", "self": "[Circular]" } WeakSet はガベージコレクションを妨げずに参照を保持するため、長期実行プロセスでのメモリリークを回避します。
BigInt 値
JSON 仕様には 64 ビット整数型がないため、JSON.stringify は BigInt 値に対して TypeError をスローします。replacer で文字列に変換します:
const data = { id: 9007199254740993n, value: 42 };
JSON.stringify(data, (key, value) =>
typeof value === "bigint" ? value.toString() : value
, 2);
// { "id": "9007199254740993", "value": 42 } Map と Set 値
Map は空のオブジェクトとしてシリアル化され、Set は空の配列としてシリアル化されます — その内容ではありません。replacer で明示的に変換します:
const data = {
tags: new Set(["json", "javascript"]),
meta: new Map([["source", "api"]]),
};
JSON.stringify(data, (key, value) => {
if (value instanceof Set) return [...value];
if (value instanceof Map) return Object.fromEntries(value);
return value;
}, 2);
// { "tags": ["json", "javascript"], "meta": { "source": "api" } } undefined 値
undefined 値を持つオブジェクトプロパティは静かに削除されます。undefined を持つ配列スロットは null になります。すべてのキーを保持する必要がある場合は、replacer を使って undefined を null に変換します:
const obj = { a: 1, b: undefined, c: null };
JSON.stringify(obj, null, 2);
// { "a": 1, "c": null } — "b" は静かに削除される
// 修正:
JSON.stringify(obj, (key, value) =>
value === undefined ? null : value
, 2);
// { "a": 1, "b": null, "c": null } ブラウザで即座に JSON をフォーマット
コードを書かずに今すぐ JSON ブロブをフォーマットする必要がある場合、Toova JSON Formatter がワンクリックで処理します — 生の JSON を貼り付け、2 または 4 スペースインデントで整形された出力を取得し、結果をコピーします。サインアップなし、ファイルアップロードなし、すべてはブラウザ内でローカルに実行されます。
形式間の変換には、JSON to YAML と JSON to CSV も同じプライバシー優先のアプローチに従います — データはデバイスから出ません。
結論
ほとんどのユースケースでは、JSON.stringify(obj, null, 2) で十分です。フィルタリング、マスキング、またはソートされたキーが必要なときは replacer 関数を追加します。外部入力を処理するときは JSON.parse を try/catch でラップします。ファイルサイズが同期解析を非実用的にするときのみストリーミングに手を伸ばします。そして、エッジケース — 循環参照、BigInt、Map/Set、undefined — は、異常なデータ形状で作業するときに頭の片隅に置いておいてください。