ToolActToolAct
JSON とは何か

JSON とは何か

生活・実用2026年8月3日15分で読める

一つの通信から始まる

私が「データを渡す」ということの意味を初めて実感したのは、開発者ツールの Network タブだった。まだ駆け出しだった頃、隣の席の先輩が「ここ、見てみろよ」と言って、失敗したリクエストをクリックした。右側のペインに展開されたのは、改行と波括弧と引用符が絡み合った、一見して無意味な文字の壁だった。

{"status":"error","code":422,"message":"email の形式が正しくありません","errors":[{"field":"email","reason":"invalid_format"}]}

当時の私には、これが「サーバーからの手紙」であると読めるようになるまで、しばらく時間がかかった。でも、ひとたび読めるようになると、これがずっと前からそこにあったかのような気がしてくる。サーバーは何を言っているのか。どの欄が必須で、なぜ弾かれたのか。すべて、この壁の中に書いてある。

二つのシステムが話をするとき、必ず「言葉」が必要になる。システムAが「この取引を承認してほしい」と思っていても、システムBに伝わるまでは、それはただの電気信号だ。そして、その言葉として、ここ四半世紀のデフォルトになったのが JSON だ。今、何かしらのWebサービスを書いている人なら、意識するかしないかに関わらず、毎日のように JSON の文字列を作り、分解し、受け取り、投げ返しているはずだ。

この記事では、そんな「当たり前すぎて逆に見えなくなっている」存在について、素朴なところから書き出してみたい。

JSON とは一体何か

JSON は JavaScript Object Notation の略で、読んで字のごとく「JavaScript のオブジェクト表記法」である。2001年、ダグラス・クロックフォード(Douglas Crockford)が、JavaScript のオブジェクトリテラルから必要最小限の部分だけを切り出し、仕様として固めたものだ。彼自身の言葉を借りれば、必要最小限のことだけを許すようにデザインされた形式だ。

ここで一つ、正直に言っておくべきことがある。この名前は実に紛らわしい。JavaScript という単語が入っているせいで「JavaScript でしか使えないんじゃないか」と思われがちだが、実際のところ JSON と JavaScript の関係は「文法の先祖がそうだった」という一点だけだ。生まれた場所が JavaScript だった、それだけの話である。今や JSON は、あらゆる言語の、あらゆる場所で、何の不自由もなく使われている。TypeScript で書いた設定も、Python のライブラリが返すレスポンスも、すべて同じ文字列として飛んでいく。

JSON の本質は、ざっくり言えば次の三点に尽きる。人間が読める。機械が読みやすい。そして、驚くほど単純。この三つを両立できている形式は、実は今もあまり多くない。

当時すでに、コンピュータ同士がデータをやり取りするための形式は存在していた。しかし、どれも「人間が読める」と「機械が読みやすい」を両立できていなかった。JSON はその両方を、ものすごく薄い仕様の上で成立させた。仕様書の厚さで言えば、数ページもあれば全部書けてしまう。

標準化の経緯も、少し変わり種だ。JSON は長い間「正式な標準」の外側にあった。クロックフォードが json.org で仕様を公開し、情報共有のメモとして RFC 4627 を提出したのが2006年。その後、ECMA-404 として2013年に標準化され(翌2014年には後継の RFC 7159 も出た)、さらに2017年には RFC 8259 としてインターネット標準に昇格した。つまり、世界中のあらゆるAPIが JSON を話し始めてから、ずっと後になって「正式に」標準になったのである。実質標準(デファクト)が先で、正式な標準が後から追いかけた。この順番は、JSON の経歴を語る上で外せない。

なぜ XML に勝ったのか

JSON が生まれる前、つまり2000年代初頭の世界を思い出してほしい。あの頃、システム間のデータ交換といえば XML だった。

そして XML の世界は、今振り返ると本当に面倒くさかった。SOAP という、XML を使ったウェブサービスの規格は、リクエストを飛ばすだけで何十行にもなる XML を組み立てる必要があった。名前空間をはじめに宣言し、エンベロープを被せ、本文を挟み込み……何をするにも儀式じみた手順があった。設定ファイルも似たようなもので、あるプロジェクトでは、たった十数個の値を設定するために電話帳のような XML を書いては、読むたびに辟易していたのを覚えている。

さらに XML には、延々と続く論争の種があった。属性(attribute)に値を書くのか、要素(element)として書くのか。名前空間(namespace)をどう振るのか。そういう設計判断の一つ一つに「正解」がなく、プロジェクトごとに流派が分かれた。しかも、そうやって書いた XML を人間が読みやすく整形するには、結局 XSLT という別の言語を覚えて変換処理を書く必要があることも多かった。データを表すために、言語が三つも四つも必要になる。さすがに重い。

その反動として、JSON は「とにかく軽い」という一点で勝った。

  • 文字数が少ない。XML の開閉タグに比べて、波括弧とカンマは圧倒的に安い。<user>...</user>{} では、桁が違う。
  • データ構造と一対一で対応する。辞書(ハッシュマップ、オブジェクト)は {}、リスト(配列)は []、文字列・数値・真偽値・null はそのまま。これは事実上、あらゆる言語が最初から持っているデータ構造そのものだ。変換のための「対応表」を覚える必要がない。
  • スキーマがなくても読める。XML はしばしば、スキーマ定義なしでは解読が困難だった。どれが要素で、どれが属性で、どれが欠けてもいいのか、分からない。JSON は定義書なしでも構造が透けて見える。
  • 学習コストがほぼゼロ。波括弧と角括弧とコロンとカンマ。以上。新しく入ってきたエンジニアに説明する手間が、まったくと言っていいほどかからない。

乱暴な言い方をすると、JSON が美しかったわけではない。周りのものが、それ以上にうんざりさせたのだ。競争相手が「電話帳」や「書式の定まらない癖のある文書」だったから、ポストイット一枚分の仕様でも十分勝てた。勝負は最初からついていた。

六つのデータ型

JSON のデータ型は、たった六つだ。それ以外は存在しない。まず、それぞれを一瞬で見てしまおう。

  • オブジェクト — {} の中に「キー: 値」のペアを並べたもの。キーは文字列で、値はどの型でもいい。{"name": "taro"}
  • 配列 — [] の中に値を順番に並べたもの。要素はどの型でも混在できる。[1, 2, 3]
  • 文字列 — 二重引用符で囲む。"こんにちは"
  • 数値 — 整数も小数も指数も、まとめて一つ。423.141e10
  • 真偽値 — truefalse。小文字で書くのが絶対条件。
  • null — 「値がない」ことを表す記号。null

これで全部だ。日付も、時刻も、バイト列も、通貨も、専用の型はない。全部、上の六つを組み合わせて表現する。

実際のデータは、こんな風に組み立てられる。例えば、あるサービスのユーザー情報を返すAPIがあったとしよう。

{
  "id": 100234,
  "name": "山田 太郎",
  "email": "taro@example.com",
  "age": 34,
  "isPremium": true,
  "tags": ["frontend", "css"],
  "address": {
    "country": "JP",
    "city": "Tokyo"
  },
  "lastLoginAt": null
}

lastLoginAt が null なのは「このユーザーはまだ一度もログインしていない」という意味を込めたつもりだ。ここで「キーごと欠けている」のと「キーがあって null が入っている」のを区別できるのが、JSON の地味に大事な性質で、この違いは後述の限界の章で改めて触れる。

ハマりやすい文法の罠

JSON は単純だが、単純であるがゆえに、逆に「この書き方は許されるんじゃないか」と甘えたくなる箇所がいくつもある。それぞれ、実際にハマったことのある人が多いと思うので、列挙しておく。

キーは必ず二重引用符で囲む。 これは初心者が最初に踏む罠だ。{name: "taro"} は JavaScript のオブジェクトリテラルとしては有効でも、JSON としては不正解。キーも文字列と同じく二重引用符が必須で、{"name": "taro"} でなければならない。

文字列は二重引用符だけ。 一重引用符は使えない。JavaScript ではどちらでもいいという緩さが、JSON では許されない。

コメントは書けない。 後述するが、これが実は一番根が深い。

末尾カンマは禁止。 配列やオブジェクトの最後の要素の後にカンマを置くと、文法エラーになる。[1, 2, 3,] はダメ。JavaScript の配列では許される書き方だけに、紛らわしい。

数値の先頭にゼロを置けない。 01 は不正。0.5 はいいが、.5 もダメで、先頭のゼロは省略できない。指数表記は 1e102.5e-3 のように書ける。

-0 は存在する。 JSON の文法では -0 も正しい数値で、-0.0 と書いても値は -0 のまま。処理系によっては 0-0 を区別することもある(JavaScript の Object.is など)から、地味にやっかい。

重複したキーは、文法上はエラーにならない。 {"a": 1, "a": 2} をパースしてもエラーにはならないが、そのとき値がどうなるかは仕様上「未定義」。実際にはほぼ全てのパーサが最後の値を採用する。しかし、ここに頼ったコードを書くのは危険だ。パーサによって挙動が違うかもしれない、という前提で動くのが安全。

Unicode は \uXXXX で書ける。 例えば \u3053 と書ける。ただし、😀 のように、絵文字などのサロゲートペア文字は、上位サロゲートと下位サロゲートを二つ並べて表現する必要がある。ここを間違えると、途切れた絵文字が画面上に現れる。エスケープの全リストは \"\\\/\b\f\n\r\t\u の九つ。このリストにない制御文字(U+0000〜U+001F)は \uXXXX で書く。それ以外の文字は、そのまま書いてよい。

こういう細かい罠は、実務では「パーサが指摘してくれる」ことがほとんどだ。だから暗記しておく必要はないが、なぜこのルールがあるのかは知っておいて損がない。ルールの背景はどれも「パースを曖昧にしないため」だ。JSON は将来の拡張のために文法に余白を残さない設計で、コメントや末尾カンマのような「書き手の都合」は一切受け付けない。それが後で効いてくる。

JSON は JavaScript の部分集合か

よく「JSON は JavaScript のオブジェクトリテラルの部分集合だ」と言われる。半分正しくて、半分正しくない。

確かに、JSON の文法は JavaScript のオブジェクトリテラルの書き方から生まれた。しかし、逆向きは成立しない。JavaScript のオブジェクトリテラルには書けて、JSON には書けないものが、山ほどある。

// どれも JavaScript としては合法だが、JSON としては全部アウト
{ name: 'taro', }          // 一重引用符、末尾カンマ
{ name: "taro", /* コメント */ }
{ run: function() {} }     // 関数
{ value: undefined }       // undefined
{ value: NaN }             // NaN、Infinity
{ date: new Date() }       // Date オブジェクト

これらは全て、JSON の文法の外だ。undefinedJSON.stringify すると、プロパティは黙って消える。NaNInfinitynull に化ける。DatetoJSON というメソッドに従って ISO 8601 の文字列になる。JavaScript 側から見ると「変換時に値がすり替わる」この振る舞いが、逆に言えば「JSON はあくまでただのデータであり、コードではない」という線引きを守っている。

歴史的なおまけを一つ。JSON が RFC 4627 として世に出たとき、そこには「JavaScript の部分集合」という表現が書かれていた。しかし後になって、これはまずいと分かった。JavaScript の古い仕様では、文字列の中に U+2028(行区切り)や U+2029(段落区切り)を直接置くと文法エラーになったからだ。JSON の文字列はこれらをそのまま含むことができてしまう。つまり「JSON は JavaScript の部分集合」という言明は、ある意味で嘘だった。「JSON.parse で読めるから」と無防備に eval に食わせると、こういう細部で死ぬ。結局、U+2028/U+2029 は JSON 文字列内でエスケープするのが実務の作法として定着した。

そして、この「JSON は JavaScript のオブジェクトリテラルではない」という区別は、実務では意外なところで効いてくる。.json ファイルを設定ファイルとして読むとき、package.json にコメントを書きたくなったとき、curl で返ってきたレスポンスをそのまま .js ファイルにコピペしたくなったとき。どれも「JavaScript と JSON は違う」と分かっていれば、エラーを見て即座に原因が分かる。

日常で出会う場所

JSON が今、どこにでもある、という話を具体的にしてみよう。

REST API のボディ。 これが最大の現場だ。POST /api/orders に JSON を投げ、GET /api/orders/1 で JSON が返ってくる。今のウェブの基本動作は、ほぼこれで出来ている。OpenAPI 仕様で API を設計するときも、中身のサンプルはたいてい JSON だ。

設定ファイル。 package.json しかり、tsconfig.json しかり。もう、挙げだすとキリがない。Node.js のエコシステムは「設定は JSON で書く」をデフォルトにしたおかげで、いちいち DSL を覚える手間を消した。GitHub Actions の workflow は YAML だが、それでも「設定の世界で JSON は外せない地位にある」のは間違いない。

ブラウザのローカルストレージ。 localStorage に構造化データを保存するとき、多くの場合 JSON 文字列にして入れる。JSON.stringify して保存し、JSON.parse して読む。IndexedDB も、構造化クローンでオブジェクトをそのまま保存できるが、通信を挟む場面では結局 JSON が出てくる。

データベース。 PostgreSQL の jsonb 型は、JSON をバイナリに変換して格納し、->->> 演算子で中のキーを直接引ける。MongoDB の BSON は、JSON をバイナリにした亜種で、ObjectIdDate など独自の型が足されている。「スキーマレスにデータを置いておきたい」という動機は、データベースの世界でも JSON への回帰を起こした。

JSON Lines。 改行で区切られた JSON を一行ずつ並べた形式(NDJSON とも呼ばれる)。ログの出力や、ストリーム処理に強い。一行が一つのイベントなので、途中で切れても「ここまで読めた」という境界が分かる。改行区切りでジョブを流すアプリは、今も多い。

WebSocket。 双方向通信のメッセージも、バイナリか JSON か、という二択になっていることが多い。チャットのサーバーがクライアントに {"type": "message", "body": "..."} を送る、というのは定番の風景だ。

fetch。 ブラウザの fetchresponse.json() と書く。この一行が、現代のフロントエンド開発の日常を形作っている。

こうして並べてみると、JSON は「一つの技術」というより「データの空気」みたいなものになっている。意識する頻度が高いからこそ、かえって見えにくい。

各言語での読み方

ここから先は、各言語でどう扱うのかの味見だけ。チュートリアルではないので、ざっくりとした印象だけ掴んでほしい。

JavaScript / TypeScript。 JSON.parse() で文字列をオブジェクトに、JSON.stringify() でオブジェクトを文字列に。この二つが標準搭載なので、何も考える必要がない。他の言語では「ライブラリを探す」ところから始まるのに、ここは最初から完備されている。JSON が JavaScript から生まれた恩恵は、こういうところに現れている。

Python。 json モジュールが標準ライブラリにある。json.loads() で文字列から、json.dumps() で文字列へ。dumpsindent=2 を渡すと、見やすい形に整形してくれる。データクラスや辞書との行き来がとても楽だ。

Java。 標準ライブラリだけでは足りず、歴史的に Jackson か Gson を使う。Jackson は ObjectMapper というクラスが中心で、POJO との相互変換が得意。Gson は Google 製で、シンプルさを売りにしている。どちらを選ぶかは宗教戦争みたいなところがあって、プロジェクトごとにどっちかに決まっている。

Go。 encoding/json が標準ライブラリ。json.Marshal / json.Unmarshal を使う。構造体に json:"name" みたいなタグを付けて対応を宣言する方式で、「構造体ありき」の設計。型が厳格なので、動的な JSON を扱うのは少し窮屈だが、そのぶん型安全に扱える。

Rust。 標準ライブラリには入っていないが、serde が事実上の標準だ。serde_json というクレートで from_str / to_string を呼ぶ。serde の特徴は、JSON に限らず色々な形式を同じ枠組みで扱える点で、一度覚えると YAML や TOML や bincode も同じ流れで読める。Rust の世界は「形式を扱う」という抽象化のレベルが一段高い。

どの言語でも、やっていることは同じだ。文字列をデータ構造に戻す(デシリアライズ)か、データ構造を文字列にする(シリアライズ)か。たったそれだけ。言語が違っても、JSON の文法は変わらない。だからこそ、言語をまたいだデータ交換が成立する。

セキュリティ:血で学んだ教訓

ここからは、多少物騒な話になる。JSON の歴史は、セキュリティの教訓とセットで進んできた。それぞれ、実際に痛い目を見た人たちがいる。

eval 災害。 2000年代初頭、JSON をパースするのに、eval を使うのは当たり前だった。JavaScript のオブジェクトリテラルは eval で評価できるし、一行で済む。しかし、eval は文字列をコードとして実行する。信頼できない通信相手が返した文字列を eval に食わせれば、相手の思うがままにコードを実行される。リモートコード実行(RCE)、そのものである。今でこそ「JSON をパースするのに eval を使うなんて」と笑い話になるが、当時は本当にそれが普通だった。この教訓が、後に JSON.parse が標準化される原動力になった。

JSONP とその妥協。 クロスドメイン通信が自由にできなかった時代、JSONP という技術が使われた。<script> タグはクロスドメインでも読み込める性質を利用し、callback({...}) という形で関数呼び出しに JSON を包んで運ぶ。eval に比べればマシだが、結局は「スクリプトとして実行する」という構造を抜け出せず、被害範囲が広がるリスクは残り続けた。CORS が普及するまでの、いわば応急処置だった。

トップレベル配列と JSON ハイジャック。 2000年代中盤の Ajax 時代、トップレベルが配列になっている JSON を <script> タグで読み込ませる攻撃が問題になった。スクリプトとして評価される際に、攻撃者が配列コンストラクタや Array.prototype のメソッドを先回りして上書きしておけば、配列の要素を横取りできる。いわゆる JSON ハイジャックで、CSRF(クロスサイトリクエストフォージェリ)の一種だ。これを受けて「API のレスポンスはトップレベルに配列を置かず、必ずオブジェクトで包む」という慣習が業界に定着した。仕様の動きはここから少しややこしい。2006 年の RFC 4627 は「JSON テキストはオブジェクトか配列」と定義していたが、2014 年の RFC 7159 がこの制限を外し、文字列や数値のような単独の値もトップレベルに置けるよう緩和した。2017 年の RFC 8259 も同じ定義を引き継いだ。「オブジェクトで包む」という慣習は今も有効な防御であり続けているが、それは仕様の強制ではなく、業界の合意として生き続けているのである。

プロトタイプ汚染。 __proto__ というキーは、文字どおり JavaScript のプロトタイプを書き換えてしまう。{"__proto__": {"isAdmin": true}} のような JSON を、対策なしのコードでオブジェクトに変換すると、意図せぬ挙動の変更を食らう。今のセキュリティ検査では、このキーがフィルタされているかがチェック項目に入っていることも多い。

JSON 爆弾。 深く入れ子になった JSON や、巨大な配列を大量に並べた JSON は、パースするだけでメモリや CPU を食い尽くす。いわゆる「爆弾」だ。10万回 [ を繰り返しただけの入力で、スタックオーバーフローやメモリ枯渇を起こせる。外部から受け取る JSON には、サイズ上限を設け、ネストの深さにも上限を設けるのが常識になっている。

現代のルールにまとめると。 パースは必ず正規のパーサに任せる。eval は絶対に使わない。受信サイズを制限する。ネストの深さを制限する。__proto__ のような危険なキーを処理する。プロトタイプへのマージを行うライブラリ(特にオブジェクトをディープマージする系)を、信頼できない入力を相手に使わない。これが、血を流して得た教科書である。

検証と拡張:JSON Schema と JSON5

JSON は自由すぎる、という不満も当然ある。特に「この API はこういう形のデータを返す」と保証したい場面では、形式だけでは足りない。

そこで生まれたのが JSON Schema だ。JSON で JSON の構造を記述する、という自己言及的な仕様で、typerequiredpropertiespattern などで「どんな JSON が正しいか」を定義できる。

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "required": ["id", "name"],
  "properties": {
    "id": { "type": "integer", "minimum": 1 },
    "name": { "type": "string" },
    "age": { "type": "integer", "minimum": 0 }
  }
}

これで、id は1以上の整数、name は必須、age は0以上、という契約を機械的に検証できる。OpenAPI のスキーマも、この思想の上に載っている。JSON Schema は標準の外にいた時期が長く、現在も一応IETFで議論は続いているが、実質的には広く受け入れられた「標準」になっている。

一方、実用主義的な拡張も乱立している。

  • JSON5 — コメント、末尾カンマ、引用符なしのキー、一重引用符、Infinity / NaN、十六進数まで許す「リラックスした JSON」。「人間が JSON を手書きするのはつらい」というモチベーションから生まれ、設定ファイル用途で使われる。
  • JSONC — JSON with Comments。コメントだけ足した亜種で、VS Code の設定ファイル(settings.json)などが実はこの形式。拡張子は .json のままだったりするから紛らわしい。
  • HJSON — Human JSON。さらに読みやすさに振り切った亜種。

これらに共通するのは、どれも正式な標準ではないという点だ。JSON5 を名乗っているからといって、パーサが必ず対応しているとは限らない。あくまで「このプロジェクトの都合に合わせた方言」であって、標準の JSON とは別物として扱うのが正しい。

その限界と不満

ここまで読んで、JSON に感じる「もやもや」を、正直に並べてみよう。どれも、どこかの現場で実際に叫ばれた不満である。

コメントが書けない。 これが最大の不満だろう。package.json に「この設定はこういう理由でつけた」と書きたい。書けない。設定ファイルという用途に、JSON は本質的に向いていない。だから JSONC や JSON5 が生まれ、YAML や TOML に設定ファイルの座を譲りつつある。

日付型がない。 日付を表す方法が、標準で定まっていない。慣習として ISO 8601 の文字列(2026-08-12T12:34:56Z)を使う人が多いが、それは「みんなが勝手にそうしてる」だけで、仕様ではない。ミリ秒の Unix タイムスタンプを数値で送る流儀もあり、"2026-08-12"2026-08-12T00:00:00Z1783900000000 の三つを見て、同じ日付だと判断するのは人間の仕事になる。日付の変換に date-fns や Joda-Time のようなライブラリが必要になるのも、この「型がない」が原因だ。

数値の精度。 JSON の数値は IEEE 754 の倍精度浮動小数点数で表現されるのが一般的で、大きな整数を正確に表せない。銀行が扱う金額や、ID のような巨大な整数を JSON で送ると、桁が化ける。9007199254740993 を JavaScript で JSON.parse すると、9007199254740992 になってしまう。このため、ID は最初から文字列で送れ、という暗黙のルールが広まった。巨大整数に手を出す業界では、JSON の数値型はむしろ罠である。

バイナリを直接入れられない。 画像やファイルの中身を JSON で送ろうとすると、base64 に変換して文字列として運ぶことになる。データ量は約4/3倍に膨らむ。WebSocket でバイナリを流す場合は、JSON ではなく生のバイナリを選ぶのが普通だ。

深いネストで死ぬ。 入れ子が深すぎる JSON を再帰パーサで処理すると、スタックオーバーフローで落ちる。JSON 爆弾でも触れたが、入力の深さに対する上限を設けていないパーサは、理論上どんな深さでも食ってしまう。

重複キーの扱いが未定義。 仕様が「未定義」とだけ言って逃げている。パーサごとに挙動が違う可能性を、いつまでも抱えている。

「キーが無い」と「null が入っている」は意味が違う。 これは JSON の長所でもあるが、どちらを送るかは仕様ではなく現場の約束事だ。{}{"lastLoginAt": null} は、どちらも有効なオブジェクトで、機械はこの違いを区別してくれる。だが「欠けている」を「未設定」と読むか「存在しない」と読むかは、API ごとに定義しなければならない。クライアントとサーバーで解釈が割れやすいので、契約として明文化しておくのが安全だ。

車輪の再発明。 日付の表現ひとつとっても、みんなが自分流にやる。ISO 8601 にするか、タイムスタンプにするか、オブジェクト {"year": ..., "month": ...} にするか。JSON が自由すぎるせいで、チームごとに「うちの流儀」が生まれ、それを覚えるのが辛い。スキーマもないので、入ってきたばかりの人が「このAPI、何が返ってくるの?」と迷う。

代替フォーマット

「JSON はもう古い」と言う人もいる。正直な比較を、それぞれ一発ずつだけしてみよう。

YAML。 人間の読み書きに最も優しい、と言われる。インデントで構造を表すので、視覚的に綺麗。だが、そのインデントと「暗黙の型変換」に由来する罠が有名だ。key: 007 が数値 7 になったり、yes が真偽値 true に化けたりする。私は YAML で日付文字列に噛まれたことがある。設定ファイルでは今も第一候補だが、データ交換の場では「読みやすいのは美点だが、曖昧さのコストが高い」と割り切るべきだ。

TOML。 設定ファイル専用に設計された形式。[section] のようなテーブル記法と、key = "value" の一行ずつの書き方。コメントも書ける。Cargo.toml や pyproject.toml でお馴染みだ。「設定は YAML より TOML がいい」という流れは、ここ数年でかなり強くなっている。ただし、データ交換用途を想定していないので、複雑な構造は表現しにくい。

Protocol Buffers。 Google が作った、コンパクトで型付きのバイナリ形式。.proto というスキーマファイルを書き、そこからコードを生成して使う。型が厳密で、フィールドに番号を付けてバイナリ化するので、JSON と比べてデータ量が大幅に小さい。ただし、スキーマ定義とコード生成というコストが常に付きまとう。JSON で「とりあえず動く」ところを、初日からこれでやる必要はない。

MessagePack / CBOR。 「JSON をバイナリにした」というコンセプトの形式。JSON とほぼ同じデータモデルを、バイナリで表現する。JSON で書いたものをそのまま変換できるのが利点で、JSON と両方向に変換するライブラリが多い。CBOR は RFC 8949 で標準化されていて、IoT や COSE(署名付きデータ)の世界で使われている。

BSON。 MongoDB が作った JSON のバイナリ亜種。ObjectIdDateBinData など、JSON にない型を追加している。MongoDB の内部ストレージ形式なので、MongoDB を使うなら自然とお世話になる。

いつどれを選ぶか。ざっくり言えば、次のような分け方になる。システム間のやり取りや、可読性が必要な場面は JSON。設定ファイルは TOML(好みで YAML)。大量データや通信量を削りたい場面は Protocol Buffers。バイナリにしたいが、手軽さを残したいなら MessagePack。 どれも、JSON の限界を埋めるために生まれた「隣人」であって、JSON を完全に駆逐する存在ではない。

結び:退屈なフォーマットほど長生きする

JSON が世に出てから、もう25年近く経つ。その間に、XML はデータ交換の主役の座から降り、SOAP はほぼ死に絶えた。しかし JSON は、変わらずそこにいる。

なぜ生き残ったのか。理由は単純で、十分に良くて、どこにでもいて、死ぬほど単純だからだ。新しくて格好いい形式が現れても、JSON ができていて、みんなが知っていて、どんな言語にもライブラリがある、という現実に勝てない。エコシステムの力は、形式の美しさよりずっと強い。今のところ JSON を「もういらない」と思ったことは一度もない。

使いどころを見極める目も、同じくらい大事だ。通信量が死活問題になるなら Protocol Buffers を選べばいい。設定ファイルなら TOML の方が正直だ。バイナリが必要なら MessagePack もある。JSON は万能ではない。だが、そのどれもが「JSON の上に築かれた土台」の上で、それぞれの役割に収まっている。

退屈なフォーマットは、長生きする。波括弧とカンマと二重引用符という、何の面白みもない部品の組み合わせ。それでも、データの受け渡しに悩んだ夜、とりあえず JSON にしておけば、朝には何かが動いている。この「考えるコストを下げてくれる」という性質こそが、四半世紀を超えて受け継がれているのだと思う。

そして、あの日先輩が画面に出してくれた、たった一行のエラー応答。今なら私は、その壁を迷わず読める。そして、次に同じ形式で返事を書く側に回っている。