APIレスポンスをターミナルに吐き出したら1行にミニファイされたJSONが返ってきた、という経験は誰にでもあると思う。設定ファイルの構文エラーがどこにあるのか分からず、括弧を目で数えた経験も。
こういうときに外部のオンラインJSON整形ツールに貼り付ける人は多いが、よく考えると、そこに貼っているJSONにはAPIキーやアクセストークン、社内データが含まれている可能性がある。サーバーに送信される前提のツールでは、機密情報の扱いが気になる場面がある。
そこで、ブラウザ内だけで完結するJSON整形・検証ツールを作った。外部送信ゼロ、ライブラリ依存ゼロで、JavaScriptの標準APIだけで動く。
JSON.parse() + JSON.stringify() で整形する基本の仕組み
JSON整形の核となるロジックは驚くほどシンプルで、JavaScriptの標準APIだけで完結する。
const parsed: unknown = JSON.parse(input)
const formatted = JSON.stringify(parsed, null, 2)
たった2行。JSON.parse() で文字列をJavaScriptオブジェクトに変換し、JSON.stringify() の第3引数にインデント幅を渡して再度文字列化する。第3引数の 2 は2スペースインデントを意味する。
この2行だけで、ミニファイされた {"name":"taro","age":30,"address":{"city":"tokyo"}} が以下のように展開される。
{
"name": "taro",
"age": 30,
"address": {
"city": "tokyo"
}
}
ポイントは JSON.parse() の戻り値を unknown 型で受けていること。any ではなく unknown にすることで、後続の処理で型安全性を維持できる。ぱんだツールズでは TypeScript の any 禁止をコーディング規約にしているため、この書き方が徹底されている。
ちなみに JSON.stringify() の第2引数は replacer 関数(または配列)で、特定のキーを除外したり値を変換したりできる。今回は null を渡して全フィールドをそのまま出力している。
パースエラーのハンドリング。SyntaxError から位置情報を取り出す
整形ツールとして重要なのは、壊れたJSONを貼り付けたときの挙動だ。JSON.parse() が不正なJSONを受け取ると SyntaxError を投げる。このエラーオブジェクトには、どこで構文解析に失敗したかの情報が含まれている。
try {
const parsed: unknown = JSON.parse(input)
setOutput(JSON.stringify(parsed, null, 2))
} catch (err: unknown) {
if (err instanceof SyntaxError) {
setError(err.message)
} else {
setError('JSON の解析に失敗しました')
}
}
err instanceof SyntaxError で型を絞り込んでからメッセージを取得している。catch の引数を unknown 型で受けることで、TypeScript の strict モードでも安全にエラーを処理できる。
ブラウザのJavaScriptエンジンが返す SyntaxError.message には、エラーが発生した位置情報が含まれる。たとえば Chrome(V8)の場合はこんなメッセージが返ってくる。
Expected property name or '}' in JSON at position 42
Firefox(SpiderMonkey)では少し違う形式で返る。
JSON.parse: expected ',' or '}' after property value in object at line 3 column 15
ブラウザによってメッセージ形式が異なるが、いずれもどこで壊れたかの手がかりが入っている。この情報をそのまま画面に表示することで、ユーザーは問題箇所を特定できる。
実際のUIでは、エラー発生時に赤背景のパネルで「構文エラー」という見出しとともにエラーメッセージを表示する。正常な入力のときだけ整形結果のテキストエリアが出現し、エラー時には出力エリアを非表示にするという切り替え方式を採用した。正常系とエラー系が画面上で混在しないため、状態が直感的に分かる。
よくあるJSON構文エラーのパターン
JSON.parse() が検出してくれるエラーには、実務で頻繁に遭遇するものが多い。
末尾カンマ(trailing comma)
{
"name": "taro",
"age": 30,
}
JavaScriptのオブジェクトリテラルでは許容されるが、JSONの仕様(RFC 8259)では末尾カンマは不正。これは特に手書きでJSONを編集したときにやりがちなミスで、JSON.parse() はきちんと弾いてくれる。
シングルクォート
{'name': 'taro'}
JSONではダブルクォートのみが有効。Pythonのdict表記やJavaScriptのオブジェクトリテラルをそのまま貼り付けると、これで引っかかることがある。
クォートなしのキー
{name: "taro"}
これもJavaScriptでは動くがJSONでは不正。設定ファイルの書き方と混同しやすいパターンだ。
こうしたエラーを JSON.parse() に検出させて即座にフィードバックすることで、ユーザーは構文ミスの箇所を素早く修正できる。
ワンクリックコピーの実装
整形結果のコピー機能は Clipboard API で実装している。
async function handleCopy() {
if (!output) return
try {
await navigator.clipboard.writeText(output)
setCopied(true)
setTimeout(() => setCopied(false), 2000)
} catch {
// Clipboard API が使えない環境では何もしない
}
}
navigator.clipboard.writeText() は非同期APIで、コピー成功後に copied フラグを true にして2秒後に戻す。UIではコピーボタンのアイコンとテキストが「コピー」から「コピー済み」に切り替わるフィードバックを返す。
try-catch で囲んでいるのは、Clipboard API がHTTPS環境またはlocalhost以外では動作しないケースがあるため。エラーが出ても画面が壊れないようにサイレントに処理している。
設計上の工夫。外部ライブラリを使わない判断
JSON整形・検証ツールの設計で最も重要な判断は「外部ライブラリを一切使わない」という選択だった。
JSONを扱うnpmパッケージは大量にある。シンタックスハイライト付きのビューワ、Tree View表示、JSONスキーマバリデーションなど、リッチな機能を提供するライブラリは豊富だ。しかし、今回はあえて JSON.parse() と JSON.stringify() だけで構成した。
理由は3つある。
バンドルサイズの最小化。 標準APIだけなら追加のJavaScriptは実質ゼロ。ページの読み込みが軽いのは、すぐ使いたいユーティリティツールにとって最も重要な体験だ。
ブラウザ互換性の心配が不要。 JSON.parse() と JSON.stringify() はすべてのモダンブラウザでサポートされている。ポリフィルもトランスパイルも要らない。
メンテナンスコストの削減。 外部ライブラリに依存しないということは、ライブラリのバージョンアップやbreaking changeに振り回されないということ。個人開発では、メンテナンスの手間をどれだけ減らせるかが継続の鍵になる。
ぱんだツールズのアーキテクチャ全体としても、「すべての処理をブラウザ内で完結させる」「ファイルをサーバーに送信しない」という方針がある。JSON整形ツールはその方針がもっとも純粋に体現されたツールと言える。サーバーサイドのコードはメタデータ生成(page.tsx での Metadata エクスポート)だけで、整形ロジックはすべてクライアントコンポーネント JsonFormatterClient.tsx に閉じている。
パフォーマンスとJSONサイズの限界
JSON.parse() と JSON.stringify() はブラウザのネイティブ実装なので、JavaScriptで手書きしたパーサーよりはるかに高速に動く。V8エンジンの場合、数MBのJSONでもミリ秒単位で処理が完了する。
ただし、ブラウザのメモリ上限がそのまま処理可能サイズの上限になる。巨大なJSON(数百MB以上)を貼り付けると、タブがクラッシュする可能性がある。この点はFAQにも「ブラウザのメモリが許す限り上限はありません」と明記して、ユーザーに判断を委ねている。
実用上は、APIレスポンスや設定ファイルの整形であれば数十KB〜数MB程度なので、パフォーマンスが問題になることはまずない。
状態管理。React の useState だけで十分な設計
UIの状態管理も最小限に抑えている。使っているstateは4つだけ。
const [input, setInput] = useState('') // 入力JSON
const [output, setOutput] = useState('') // 整形結果
const [error, setError] = useState<string | null>(null) // エラーメッセージ
const [copied, setCopied] = useState(false) // コピー済みフラグ
状態管理ライブラリ(Zustand、Jotai など)は使っていない。入力→処理→出力という単方向のフローで、状態の依存関係がシンプルだからだ。入力テキストが変更されるたびに error と output をクリアすることで、前回の処理結果が残り続けるという混乱を防いでいる。
onChange={(e) => {
setInput(e.target.value)
setError(null)
setOutput('')
}}
この「入力が変わったら出力をリセットする」パターンは、変換系ツール全般で使える設計だ。
まとめ
JSON整形は JSON.parse() + JSON.stringify() の2行で実現できるシンプルな処理だが、実用的なツールにするには、エラーハンドリング、コピー機能、状態管理のリセット戦略など、地味だが重要な設計判断が積み重なっている。
外部ライブラリに頼らず標準APIだけで構成することで、バンドルサイズ最小・メンテナンスコスト最小のツールになった。ブラウザ完結でデータが外部に送信されないため、APIキーや社内データを含むJSONでも安心して整形・検証できる。
実際に使いたい場合はこちらから。
ぱんだツールズ では他にも PDF・画像・CSV・テキスト処理などの開発者向けツールを公開中。すべて無料・登録不要・ブラウザ完結で使える。
https://sakutto-panda.com
この記事は Zenn にも同じ内容を投稿しています。