JSON の派生フォーマットたち
はじめに
最近 JSONP(詳細は後述します)というものに触れる機会がありました。 VSCode の設定ファイルで使われる JSONC は知っていたのですが、他にも派生のフォーマットもあるんだと知りました。 ということで、他にもっとないか個人的な興味から調べてまとめました。
そもそも JSON って
JavaScript Object Notation の略で、データを表現するためのテキスト形式です。
{
"name": "John Doe",
"age": 30,
"city": "New York"
} こんな感じでキーと値のペアでデータを表現します。
なぜ普及したのか?
人間にとって比較的読み書きしやすく、機械にとってもパース・生成が簡単に行えます。 また、JavaScript のオブジェクト記法が由来のため、C系の言語との親和性が高いです。 そのため、多くの言語の標準機能で実装されており、APIのレスポンス、設定ファイルなどでよく使われています。
なぜ派生するのか?
扱いやすく、多くの言語で実装されている JSON でも弱点はあります。
- コメントが書けない
- テキストなので容量が大きくなりがち
- 末尾カンマが許されない
- 文字列はダブルクオート必須
- 型の種類が少なく、バイナリデータや日付型がない
上記のような制約があるため、手での書き心地は微妙で、データフォーマットの網羅性も足りません。 多くのケースで有用であるが、ところどころ適さないって感じですね。
テキスト系
まずは、テキストとして人間寄りに派生したフォーマットを紹介します。
JSON5
JSON5 は制約を緩和するために提案された拡張フォーマットで、ECMAScript 5.1 の構文を一部取り込んだ JSON のスーパーセットです。(5 の由来は ECMAScript 5.1 、通称 ES5 だと個人的に推測。)
JSON にない以下の機能が使えます。
- オブジェクトキーを引用符なしで書ける(有効なJS識別子であれば)
- オブジェクト・配列の末尾カンマOK
- シングルクォート文字列
- 複数行文字列
- エスケープ文字列(\nなど)
- 16進数リテラル(0xDEADbeefなど)
- Infinity・NaN・先頭の+符号
- // と /* */ コメント
{
// コメントが書ける
name: 'my-app', // キーに引用符不要
version: 2.0,
port: 0x1F90, // 16進数もOK
features: [
"hoge \ // 複数行文字列
huga\\n", // エスケープ
'piyo', // 末尾カンマOK
],
} Babel の設定ファイルは babel.config.json ですが、JSON5 として解析されるほか、ESLint の公式プラグイン @eslint/json では JSON 以外に JSON5 も対象にできます。
JSONC
JSON with Comments の略で、JSONにコメントを追加できるようにした拡張フォーマットです。
VSCode の設定ファイル settings.json で使われているので、よく目にすると思います。
ただ、VSCode 内でのローカルルールみたいな感じで詳細な仕様は存在しないようです。
こちらも @eslint/json で扱えるようです。
バイナリ系
次は、バイナリ形式にして小サイズに特化した派生フォーマットです。
BSON
Binary JSON の略で、JSON を拡張したバイナリ形式のデータ交換フォーマットです。 MongoDB のためのフォーマットのため、ほぼすべての主要なバックエンドの言語では扱うことができます。 ただし、MongoDB のドライバが裏で自動的に BSON に変換しているため、普段開発者が意識することはないです。 BSONは3つの特性を持つよう設計されています。
- ネットワーク経由が前提のためサイズが軽量
- トラバースが容易
- エンコード・デコードが効率的
(ただし、長さのプレフィックスや配列インデックスが付くため、場合によっては JSON よりサイズが大きくなることもあります。)
また、JSON には無い型(日付型・バイト配列・IEEE 754準拠の浮動小数点など)を持ち、厳密な型指定ができます。
MessagePack
JSON から徹底的に無駄を削ぎ落として、省サイズ・高速転送に特化したものです。 「It’s like JSON. but fast and small.」を謳っており、ネットワーク転送やストレージ効率を重視する場面で使われます。
JSON との互換性が高く、JSON で表現できるデータはそのまま MessagePack に変換できます。 ただし、BSON のような拡張型(日付型・バイナリ型など)は標準では持たず、あくまで JSON の型体系をコンパクトにしたものです。 主な用途として、ログ転送、リアルタイム通信(ゲームや IoT)などがあります。多くの言語で公式・非公式のライブラリが提供されており、導入しやすいのも特徴です。
ハック系
最後は、特定の課題をハックするために派生したフォーマットです。
JSONP
JSON with Padding の略で、CORS問題を回避するために JSON をコールバック関数でラップして返す手法です。 CORS が普及する前の苦肉の策のため 現在ではセキュリティの観点から非推奨 です。
callback({
"name": "John Doe",
"age": 30,
"city": "New York"
}) ブラウザには同一オリジンポリシーがあり、異なるドメインへの Ajax 通信が制限されています。 しかし、script タグなら異なるドメインでも読み込めるため、この問題を回避できます。
<!-- バックが対応していたら callback 名を指定可能 -->
<script src="https://api.example.com/data?callback=myFunc"></script> CORS(Cross-Origin Resource Sharing)がブラウザに実装され、サーバー側でヘッダーを設定するだけで安全にクロスオリジン通信ができるようになったため、JSONP は完全に不要になりました。
JSONL
JSON Lines の略で、1行に付き1つのオブジェクトを書くフォーマットです。 以下の例を見ればすぐにわかります。
{"id": 1, "name": "Alice", "age": 25}
{"id": 2, "name": "Bob", "age": 30}
{"id": 3, "name": "Charlie", "age": 22} オブジェクトの区切りは改行で、配列 [] でまとめる必要はありません。
通常の JSON はパース処理の前に全体読み込みが必要ですが、 JSONL は 1行ずつ読み込みができます。
なので、巨大なファイルであっても CSV のように先頭から順次処理できます。
また、最後尾へのオブジェクト追加もファイルの最後に追記するだけです。