ByteScope

JSON ファイル形式

JavaScript Object Notation。軽量な構造化データ形式。

MIME タイプ
application/json
拡張子
.json
カテゴリ
テキスト・マークアップ

マジックナンバー(ファイル署名)

'{' または '[' で始まり、引用符付きのキーか入れ子の括弧が続くテキスト。

JSON 形式について

JSON は JavaScript のオブジェクトリテラル構文の部分集合で、Douglas Crockford が発明したというより書き留めたものであり、仕様全体が数ページに余裕で収まります。型は 6 つ(オブジェクト、配列、文字列、数値、真偽値、null)だけで、日付もバイナリもコメントもバージョンもありません。この欠落はどれも意図的で、そしてどれもどこかで回避されています。日付は ISO の文字列に、バイナリは base64 に、コメントは誰も読まないキーに、バージョン管理は読む側が確認し忘れないよう祈るフィールドに。極小の文法と普遍的な対応というこの組み合わせが勝因であり、同時に、実務上の JSON の問題がほぼすべて「形式が何も言っていないこと」に関するものである理由でもあります。

ヘッダーの構造(バイト単位)

オフセット長さフィールド意味
01First structural character先頭の空白を除いて、オブジェクトなら 7B、配列なら 5B。現行の標準では JSON テキストは任意の値でよいので、裸の数値や引用符付きの文字列も合法ですが、置き換えられた 2006 年の仕様はオブジェクトと配列しか許しておらず、現存する生成器はほぼすべて今もそのどちらかを吐きます。
04Encoding-detection windowJSON テキストの最初の 2 文字は必ず ASCII なので、先頭 4 バイトのゼロの並び方でエンコーディングが分かります。00 00 00 xx なら UTF-32 ビッグエンディアン、00 xx 00 xx なら UTF-16 ビッグエンディアン、xx 00 00 00 なら UTF-32 リトルエンディアン、xx 00 xx 00 なら UTF-16 リトルエンディアン、4 バイトとも非ゼロなら UTF-8。今どきの JSON は通信路上では UTF-8 でなければならないので、この検査が実際に教えてくれるのは「どうすべきか」ではなく「何が起きたか」です。
03UTF-8 byte-order markEF BB BF。標準は「実装は付けてはならない」と定め、パーサーが無視することは許しています。ところが Node やブラウザの背後にある JavaScript エンジンはそのどちらもせず、これで例外を投げます。だから Windows のエディタが付けた BOM は、テキストが見た目には何も変わっていないのに設定ファイルが読み込まれなくなる、ごくありふれた原因になります。
31First structural character behind a markBOM があるとき、文書が実際に始まる位置です。わざわざ 1 項目として書くのは、パーサーが報告するエラーが「位置 0 に予期しないトークン」と言うのに、その位置 0 の文字が、それを探すために使うどのエディタでも見えないからです。
0variableEverything elseヘッダがありません。マジックナンバーも、長さも、バージョンも、チェックサムも、トレーラーも、インデックスも無い。JSON ファイルは最初のバイトから最後のバイトまで中身であり、読み手が知りたいであろう性質(どれくらい大きいか、完全かどうか、どんな形をしているか)は、全部パースして初めて得られます。

似た形式との見分け方

紛らわしい形式見分け方
NDJSON/JSON Lines先頭はバイト単位で同一です。どちらも開き波括弧から始まります。違うのは JSON Lines が 1 行に完結した文書を 1 つずつ置き、全体を配列で囲まず、行間にカンマも置かないこと。だから標準のパーサーは 1 行目を読み終えてから、2 行目で「JSON の終わりの後に予期しない文字」と言って落ちます。ログのパイプラインやデータセットのダンプがこれを使うのは、追記できて 1 行ずつ読めるからです。
JSONC / JSON5これもオフセット 0 では同一で、最初の // コメントか末尾カンマに達した瞬間に不正になります。tsconfig.json やいくつかのエディタの設定ファイルは JSONC であって JSON ではありません。厳格なパーサーでそれらを読むプログラムが、それを書いたエディタは何も言わずに受け付けるファイルで落ちるのはこのためです。
XML波括弧や角括弧と山括弧なので、見分けるのは造作もありません。本当に意味があるのは用途の比較のほうです。JSON にはリストを表す方法がちょうど 1 つしかなく、マークアップを含んだテキストを表す方法は 1 つもありません。データが JSON へ移り、文書が移らなかった理由がそこにあります。
CBOR/MessagePack/BSONほぼ同じデータモデルのバイナリ符号化で、どれもテキストではありません。CBOR はマップを A0 から BF の範囲の先頭 1 バイトで書きます。BSON はリトルエンディアンの 32 ビットの全体長で始まるので、最初のバイトはたいてい小さくて印字できない値です。JSON のはずのファイルがバイナリとして開いたなら、答えはこのあたりで、どこも壊れてはいません。
JSONPオブジェクトの前にコールバック名と開き丸括弧が付いたもので、データの文書ではなく実行可能な JavaScript の文です。判定規則は 1 バイト目から適用できるほど単純で、英字で始まるものは JSON ではありません。

よくある壊れ方

  • 途中切れ。長さもトレーラーも無いので、切られた文書は「入力が予期せず終わった」で落ち、どれだけ失われたかを知る手立てはありません。最後の flush をせずに書かれたストリームは閉じ波括弧だけを失うことが多く、だから一番よく効く修復が「閉じ波括弧を 1 つ足す」だったりします。
  • バイトオーダーマーク。どのエディタも表示しない 3 バイトが、それ以外は完璧なファイルの先頭に付いていて、位置 0 を名指しするパースエラーを生みます。hex ビューアで先頭 3 バイトを確認すれば、半日溶かしかねない問題が数秒で片付きます。
  • 末尾の余計なデータ。文書を 2 つ連結した、ファイルにログの行を追記した。すると「JSON の後ろに空白以外の文字がある」と、位置付きで報告されます。ただしその位置はデコード後のテキストの文字数で数えられていてバイト数ではないので、非 ASCII を含むファイルでは hex エディタのカーソルと一致しません。
  • 数値が別の数値になって返ってくる。この形式の数値型は 1 つきりで精度も規定していないため、IEEE 754 の倍精度で作られた読み手同士が厳密に一致するのは、-2^53 + 1 から 2^53 - 1 までの整数だけです。9007199254740993 という識別子は、読み書きを 1 往復すると 9007199254740992 になります。しかもどこでもエラーは上がりません。ディスク上のバイトは正しく、メモリ上の値だけが正しくなかったのです。これを踏んだシステムは、以後、識別子を文字列で運ぶようになります。
  • 対になっていないサロゲート。\uD800 のようなエスケープが対になる相手なしに入っていても、文法は 16 進 4 桁なら何でも受け付けるのでパースは成功し、そして妥当な UTF-8 として書き戻せなくなります。向こう側から出てくるのは置換文字なので、どの段階も成功と報告した往復の後で、データだけが静かに変わっています。

この形式の現在地

JSON はネットワークに関わるものすべての既定のデータ形式で、その地位が本気で脅かされてはいません。REST も GraphQL もこれを話しますし、パッケージのマニフェストもロックファイルもこれで書かれ、ログのパイプラインは 1 行 1 ドキュメントで吐き、どの言語も標準ライブラリにパーサーを積んでいます。むしろ示唆的なのは、意図的に使われない場所のほうです。設定ファイルが YAML や TOML へ流れ続けているのは、JSON にコメントも末尾カンマも無いから。技術的というより使い勝手の不満ですが、それでも決定的でした。大量の内部通信は Protocol Buffers や CBOR、MessagePack へ移ります。テキストのパースは長さ接頭辞付きのバイナリより高くつきますし、スキーマがあることはビルド手順を増やすだけの価値があるからです。非常に大きな表データは Parquet へ行きます。JSON は行ごとに全部のキーを繰り返すからです。それでも JSON が第一候補であり続ける理由は、人が読めることです。他人が実装しに来る API、誰かが夜中の 3 時に編集する設定ファイル、そして眺めながらデバッグされるもの。そういうものにとって、この非効率さは、端末上で自明であることの対価でしかありません。

JSON ファイルをこのサイトで開く

どれもブラウザ内で動きます。ファイルが端末から出ることはありません。

関連する形式

JSON と混同しやすい形式です。先頭バイトを見比べてください。

形式先頭バイト拡張子
XML固定の署名なし.xml
HTML固定の署名なし.html

よくある質問

ファイルのマジックナンバーとは何ですか?

ファイル先頭の数バイトで、名前が何であろうとその形式を示すものです。PNG は必ず 89 50 4E 47 で始まり、ZIP は 50 4B 03 04 で始まります。OS もブラウザも file コマンドも、拡張子を信用せずこのバイト列を読みます。だからファイル名を変えても中身の正体は変わりません。

マジックナンバーを自分で確認する方法は?

macOS や Linux なら xxd -l 16 ファイル名 で先頭 16 バイトが表示されます。Windows なら PowerShell で Format-Hex -Path ファイル名 -Count 16 です。あるいはこのサイトのファイル形式判別ツールにドロップすれば、ローカルでバイト列を読んで形式を教えてくれます。アップロードはありません。

2 つの形式が同じマジックナンバーを持つのはなぜ?

一方が他方の上に作られているからです。docx、xlsx、pptx、apk、jar、epub はすべて中身が ZIP アーカイブなので ZIP の署名で始まり、区別するには内部のエントリを見る必要があります。Java の class ファイルと macOS のユニバーサルバイナリはどちらも CA FE BA BE で始まり、直後のフィールドで見分けます。

マジックナンバーがまったくないファイルもある?

あります。XML、HTML、JSON、SVG、CSV といったテキストベースの形式には固定のバイト署名がないため、構造から判別します。先頭の <?xml 宣言、<svg 要素、あるいは { に続く引用符付きのキーなどです。これは確定ではなく推測なので、このリファレンスではそうした形式をプローブ判別として示しています。

マジックナンバーが一致すればそのファイルは安全?

いいえ。わかるのは形式であって、中身が無害かどうかではありません。正規の PDF や docx でも悪意のあるペイロードを含んでいることがあります。特に拡張子とバイト列が食い違うときに真の形式を知るのは有用な第一段階ですが、マルウェアスキャンではありません。

JSON かもしれないファイルをお持ちですか?ファイル形式判別ツールで調べる — マジックナンバーをローカルで読み取ります。アップロードは一切ありません。またはすべての形式を見る。