Web●○○○○

JSON

JSON ・ じぇいそん

キーと値の組と配列を入れ子にしてデータを表すテキスト形式。6つの型だけに絞った、API 通信の共通語。

✦ 30秒で試す — 航海送った値と、届いた値の「種類」が変わっている受け取った側で、作成日時の値は何になっている?予想してみる

概要

JSON(JavaScript Object Notation)は、構造を持つデータをテキストで表現するためのフォーマットです。{"name": "Sato", "age": 28, "tags": ["dev", "tokyo"]} のように、キーと値の組(オブジェクト)と値の並び(配列)を入れ子にしてデータを組み立てます。名前のとおり JavaScript のオブジェクト記法に由来しますが、フォーマット自体は言語から独立しており、主要なプログラミング言語のほぼすべてが読み書きできます。

今日のシステム間のデータ交換は、大部分がJSONで行われています。Web API のリクエストとレスポンス、アプリケーションの設定ファイル(package.json など)、構造化されたログ、NoSQL データベースのドキュメント——「システムの境界をデータが越える場面」には、たいていJSONがいます。

なぜ生まれたか

2000年代初頭、システム間のデータ交換の標準は XML でした。しかし XML はタグの開閉で冗長になりがちで、スキーマや名前空間などの周辺仕様も複雑、パースの処理も重い。特にブラウザ上の JavaScript からサーバとデータをやり取りする用途では、明らかに大げさでした。

ダグラス・クロックフォードは、JavaScript にもともと備わっているオブジェクトリテラル記法の一部をそのまま切り出せば、軽量なデータ形式として十分使えることに着目し、2000年代前半に JSON として仕様化しました。「新しく発明したのではなく、発見した」と彼が語るとおり、仕様書はわずか数ページです。Ajax の流行でブラウザとサーバの非同期通信が爆発的に増えると、パースが軽く人間にも読める JSON は急速に XML を置き換え、REST API の事実上の標準フォーマットになりました。その後 RFC 8259 と ECMA-404 として標準化され、今では言語や OS を問わず読み書きできる、いちばん「無難な」データ形式になっています。

詳細

割り切った仕様——6つのデータ型しかない

JSON が表現できるのは、文字列・数値・真偽値(true/false)・null・配列・オブジェクトの6種類だけです。このうち配列とオブジェクトは入れ物で、中に6種類のどれでも入れられるので、入れ子にすれば木の形をしたデータを表せます。日付型もコメントも変数もありません。この徹底した割り切りが、あらゆる言語での実装を容易にし、「どの環境でも同じように解釈できる」という最大の価値を生みました。表現力の乏しさは弱点ではなく、相互運用性のための意図的な設計です。

文法上の注意点はいくつかあります。キーは必ずダブルクォートで囲む({name: "x"} は JavaScript としては有効でも JSON としては不正)、末尾カンマは許されない、コメントが書けない——JavaScript のオブジェクトリテラルより厳格である点が、初心者がつまずきやすいところです。

シリアライズとデシリアライズ

プログラム内のオブジェクトはメモリ上の構造であり、そのままネットワークに流したりファイルに保存したりはできません。オブジェクトを JSON 文字列に変換することをシリアライズ(直列化)、その逆をデシリアライズと呼びます。JavaScript なら JSON.stringify / JSON.parse、Python なら json.dumps / json.loads が対応します。

この変換は行きと帰りの両方で起こります。

サーバ 任意の言語ブラウザ JavaScriptサーバ 任意の言語ブラウザ JavaScriptオブジェクトを stringify で文字列化parse で自分の言語のオブジェクトに復元処理して結果のオブジェクトを作るparse でオブジェクトに戻して利用HTTPリクエスト ボディはJSON文字列HTTPレスポンス ボディはJSON文字列

HTTP で JSON を送るときは、ヘッダに Content-Type: application/json を付けて「このボディは JSON です」と宣言します。異なる言語で書かれたシステム同士でも、この往復さえ成立すればデータを共有できる——JSON が「API の共通語」と呼ばれる理由です。

型が消える、という宿命

JSON の割り切りには代償もあります。送る側の言語が持っている型のうち、6種類に写せないものは、変換のたびに形を変えるか、消えるか、送れなくなります。JavaScript の場合を並べると次のとおりです。

JavaScript の値回線を流れる JSON の文字列JSON.parse で戻した値文字列・数・真偽値null・配列・オブジェクトそのまま書ける同じ型で戻るDate"2026-09-25T09:00:00.000Z"ただの文字列undefined項目ごと省かれる項目がないNaN・Infinitynullnull(欠損に化ける)BigIntTypeError で止まる届かない他言語の int6490071992547409939007199254740992
JavaScript の値を JSON.stringify して JSON.parse で戻すと何が起きるか。6つの型に写せないものは形が変わるか、消えるか、送れない

もっとも典型的なのが日付で、JSON に日付型はないため "2026-09-25T09:00:00.000Z" のような文字列(ISO 8601 形式が慣例)として送り、受け取った側が日付として解釈し直す必要があります。どの文字列が日付なのかは JSON 自体には書かれていないので、それを知っているのは送り手と受け手の取り決め(スキーマ)だけです。undefined はオブジェクトの中では項目ごと省かれ(配列の中なら null になります)、NaN や Infinity は例外にもならずに null へ化けます。

数値の精度も落とし穴です。JSON の仕様は数の桁数を制限していませんが、JavaScript の数値は64ビットの浮動小数点数で、正確に表せる整数は 2⁵³(9007199254740992)までです。他言語のサーバが64ビット整数の ID を数のまま書くと、9007199254740993 は受け取ったブラウザの JSON.parse で黙って 9007199254740992 に丸まります。だから ID のような大きな整数を文字列で返す API は珍しくありません。JavaScript の BigInt は、逆に JSON.stringify が例外を投げて送れないようになっています(黙って桁を落とすよりは、止めたほうが安全という判断です)。

棚の値を運んで確かめてください。変換と復元には、ブラウザ標準の JSON.stringify と JSON.parse を本当に使っています。

⚡ 体験: JSON を往復すると型はどうなるか
受け取り方
送る側のメモリ(JavaScript)受け取った側のメモリJSON 文字列 →ここへ運ぶ棚の値(送る側へ運ぶ)name: "Sato"age: 28tags: ["dev", …]createdAt: Datenickname: undefinedscore: NaNroles: Mapid: 2⁵³+1(BigInt)orderNo: 2⁵³+1(Go の int64)
回線を流れた文字列(JSON.stringify の結果)
まだ何も送っていません
JSON.parse(text) の結果
まだ何も届いていません
○ Date が文字列になる○ undefined が消える・NaN が null になる○ BigInt は送れない○ 2⁵³ を超える整数が丸まる○ 受け取る側で日付を戻す

下の棚の値を、左の送る側のオブジェクトへドラッグしてください(タップでも入ります)。入れるたびにブラウザの JSON.stringify で文字列になって回線を渡り、右で JSON.parse されます。まずは name や createdAt から。

文字列への変換と復元は、この部品の中でブラウザ標準の JSON.stringify と JSON.parse を本当に呼んでいます。orderNo だけは、他言語(Go の int64 など)のサーバが数のまま書いた JSON の文字列を再現して差し込んでいます。

「JSON を介すると型情報が一段落ちる」ことを前提に、境界でのバリデーション(スキーマ検証)を設けて、届いた値を自分の言語の型に戻すのが実務の定石です。TypeScript なら、JSON.parse の戻り値は中身を確かめるまで信用できない値として扱い、zod のような検証ライブラリで型を確かめてから使います。

スキーマと周辺エコシステム

JSON 自体には「このデータはこの構造であるべき」という取り決めがありませんが、それを補う仕組みとして JSON Schema があり、API の仕様記述(OpenAPI)やエディタの設定ファイル補完に使われています。また、コメントや末尾カンマを許した JSONC(VS Code の設定など)、1行に1つの JSON を並べる JSON Lines(途中まで読んでも壊れず、追記もしやすいので、構造化されたログの出力に向きます)、人間が書く設定用の YAML など、用途に応じた派生・隣接フォーマットも押さえておくと役立ちます。YAML は 1.2 以降で JSON をほぼ丸ごと含むように整理されたので、JSON の文書はたいてい YAML としても読めますが、1.1 系のパーサ(no を false と読むなど)ではそうとは限りません。

実務での使いどころと注意点

API レスポンスの設計では、「一貫したキーの命名(camelCase か snake_case か)」「エラー時も同じ構造で返す」「値がないときは項目を省くのか null を入れるのかを決める」といった規約を揃えることが、クライアント実装のしやすさに直結します。セキュリティ面では、受け取った JSON を検証せずに信用しない、eval でパースしない(必ず JSON.parse を使う)、といった基本を守ること。また巨大な JSON を丸ごとメモリに載せるとパースが重くなるため、大量データではストリーミングパースやページネーションを検討します。性能やサイズが問題になる内部通信では、スキーマを共有してバイナリで運ぶ gRPC(Protocol Buffers)のような選択肢もあります。シンプルなフォーマットだからこそ、運用の規律が品質を分けます。

試問

下の棚には、文字列・数・配列・日付(Date)・undefined・NaN・Map・大きな整数(BigInt と、他言語のサーバが書いた int64)が並んでいます。棚の値を左の「送る側」へ運ぶたびに、ブラウザ標準の JSON.stringify で文字列になって右へ渡り、JSON.parse で読み戻されます。受け取り方は「日付を戻す reviver 付き」に切り替えられ、送る側の id の行を押すと BigInt と文字列を切り替えられます。

✦ 命題挑戦中

送る側に値を5つ以上入れ、JSON を往復しても1つも型が変わらず、消えずに届くようにせよ

  • 送る側に値を 5 つ以上入れて、送れている(いま 0 つ)
  • 受け取った側で、型が変わった・消えた項目が 0(すべて緑)
⚡ 体験: 5つの値を、型を変えずに往復させる
受け取り方
送る側のメモリ(JavaScript)受け取った側のメモリJSON 文字列 →ここへ運ぶ棚の値(送る側へ運ぶ)name: "Sato"age: 28tags: ["dev", …]createdAt: Datenickname: undefinedscore: NaNroles: Mapid: 2⁵³+1(BigInt)orderNo: 2⁵³+1(Go の int64)
回線を流れた文字列(JSON.stringify の結果)
まだ何も送っていません
JSON.parse(text) の結果
まだ何も届いていません
○ Date が文字列になる○ undefined が消える・NaN が null になる○ BigInt は送れない○ 2⁵³ を超える整数が丸まる○ 受け取る側で日付を戻す

下の棚の値を、左の送る側のオブジェクトへドラッグしてください(タップでも入ります)。入れるたびにブラウザの JSON.stringify で文字列になって回線を渡り、右で JSON.parse されます。まずは name や createdAt から。

文字列への変換と復元は、この部品の中でブラウザ標準の JSON.stringify と JSON.parse を本当に呼んでいます。orderNo だけは、他言語(Go の int64 など)のサーバが数のまま書いた JSON の文字列を再現して差し込んでいます。