0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

JSON整形・バリデーションをブラウザ完結で実装する — 標準APIだけで作るフォーマッター【ライブラリ不要】

0
Posted at

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 など)は使っていない。入力→処理→出力という単方向のフローで、状態の依存関係がシンプルだからだ。入力テキストが変更されるたびに erroroutput をクリアすることで、前回の処理結果が残り続けるという混乱を防いでいる。

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 にも同じ内容を投稿しています。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?