JSONはWeb開発者にとってお馴染みのデータフォーマットですが、大規模データ処理やAI分野では「JSONL」というフォーマットが広く使われています。この記事では、JSONとJSONLそれぞれの仕様を整理し、両者の違いと使い分けを解説します。
JSONとは
JSON(JavaScript Object Notation)は、RFC 8259 で定義されたテキストベースの軽量データ交換フォーマットです。
{ "users": [ { "id": 1, "name": "Alice", "age": 30 }, { "id": 2, "name": "Bob", "age": 25 }, { "id": 3, "name": "Charlie", "age": 35 } ]}主な特徴は以下の通りです。
- MIMEタイプ:
application/json - ファイル拡張子:
.json - エンコーディング: UTF-8
- データ型: 文字列、数値、真偽値、null、オブジェクト、配列をサポート
- 整形(pretty-print): インデントや改行を自由に入れて読みやすくできる
ほぼすべてのプログラミング言語にパーサーが組み込まれており、REST APIの通信や設定ファイルなど幅広い用途で使われています。
JSONLとは
JSONL(JSON Lines)は、jsonlines.org で定義されたテキスト形式です。RFCのような公式な標準化プロセスは経ていませんが、デファクトスタンダードとして広く採用されています。
{"id": 1, "name": "Alice", "age": 30}{"id": 2, "name": "Bob", "age": 25}{"id": 3, "name": "Charlie", "age": 35}JSONLのルールは非常にシンプルで、以下の3つだけです。
- UTF-8エンコーディング: BOM(U+FEFF)は含めない
- 各行が有効なJSON値: オブジェクト、配列、文字列、数値などいずれも可
- 改行区切り:
\n(LF)で行を分離する。最終行の改行は推奨
JSONと異なり、ファイル全体を [] で囲んだり、行間にカンマを入れたりする必要はありません。各行が独立した1つのJSONとして成立しています。
NDJSONとの関係
NDJSON(Newline Delimited JSON)という名前を見かけることもありますが、JSONLとNDJSONは同一のフォーマットです。開発者コミュニティではJSONL、エンタープライズ向けの文脈ではNDJSONと呼ばれる傾向がありますが、パース規則やフォーマット仕様に違いはありません。
JSONとJSONLの比較
両者の違いを表にまとめます。
| 観点 | JSON | JSONL |
|---|---|---|
| 標準化 | RFC 8259(IETF正式標準) | jsonlines.org(デファクト標準) |
| MIMEタイプ | application/json | application/jsonl(未登録) |
| ファイル拡張子 | .json | .jsonl |
| 構造 | 1つの値(オブジェクトまたは配列) | 1行1レコード |
| 整形 | pretty-print可能 | 不可(1行に収める必要あり) |
| レコード区切り | カンマ , | 改行 \n |
| 外側のラッパー | {} や [] で囲む | なし |
パース方法の違い
JSON はファイル全体を読み込んでから一括でパースします。ファイルの途中に構文エラーがあると、全体がパース不能になります。
JSONL は1行ずつ読み込んでパースできます(ストリーム処理)。1行が壊れていても他の行には影響しないため、エラー耐性が高いのが特徴です。
メモリ使用量の違い
ここが最も実用上の差が出るポイントです。
- JSON: ファイル全体をメモリに展開する必要がある。100MBのJSONファイルには100MB以上のメモリが必要
- JSONL: 1行分のメモリだけで処理できる。数GBのファイルでも数KB〜数MBのメモリで処理可能
大規模データを扱う場面では、この違いが決定的に重要になります。
ユースケース
JSONが適するケース
- REST APIのリクエスト/レスポンス: Web APIの標準フォーマット
- 設定ファイル:
package.json、tsconfig.jsonなど - ネストされた複雑なデータ構造: 階層的なデータの表現
- 小〜中規模のデータ: メモリに収まるサイズのデータ
- 人間が読み書きする場面: pretty-printによる可読性の確保
JSONLが適するケース
- 大規模データの処理: 数GB〜TBクラスのデータセット
- ログ収集・構造化ロギング: アプリケーションログの記録
- データストリーミング: リアルタイムデータ処理
- 機械学習の学習データ: モデルの学習・評価データセット
- 追記(append)が多い場面: ファイル末尾への追加が容易
- 分散処理: 行単位での並列処理が可能
実際の採用例
JSONLは多くのサービスやツールで採用されています。
OpenAI API では、ファインチューニングの学習データやBatch APIの入出力にJSONL形式を使用しています。
{"messages": [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": "What is 2+2?"}, {"role": "assistant", "content": "4"}]}{"messages": [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": "Capital of France?"}, {"role": "assistant", "content": "Paris."}]}Google BigQuery では、データの取り込み・エクスポート時に NEWLINE_DELIMITED_JSON 形式をサポートしています。
Elasticsearch の Bulk API は、NDJSON形式でのリクエストを採用しています。
{"index": {"_index": "products", "_id": "1"}}{"name": "Laptop", "price": 999}{"index": {"_index": "products", "_id": "2"}}{"name": "Phone", "price": 699}その他にも、Hugging Face Datasets(機械学習データセットの配布)、Docker(ログドライバーの出力)、Fluentd / Fluent Bit(ログ収集)など、多くのツールやプラットフォームでJSONLが活用されています。
コードで扱う
JavaScriptでの読み書きの例を紹介します。
JSONLの読み込み(ストリーム処理)
Node.jsの readline モジュールを使うと、行単位で効率よく処理できます。
import { createReadStream } from "fs";import { createInterface } from "readline";
async function readJsonl(filePath) { const stream = createReadStream(filePath, { encoding: "utf-8" }); const rl = createInterface({ input: stream, crlfDelay: Infinity });
const records = []; for await (const line of rl) { if (line.trim()) { records.push(JSON.parse(line)); } } return records;}大規模ファイルの場合は、全件を配列に溜めずに1行ずつ処理するとメモリ効率が良くなります。
async function processJsonl(filePath, handler) { const stream = createReadStream(filePath, { encoding: "utf-8" }); const rl = createInterface({ input: stream, crlfDelay: Infinity });
let lineNumber = 0; for await (const line of rl) { lineNumber++; if (!line.trim()) continue; try { await handler(JSON.parse(line), lineNumber); } catch (error) { console.error(`Line ${lineNumber}: ${error.message}`); } }}JSONLの書き込み
import { writeFileSync, appendFileSync } from "fs";
// 一括書き込みfunction writeJsonl(filePath, records) { const content = records.map((r) => JSON.stringify(r)).join("\n") + "\n"; writeFileSync(filePath, content, "utf-8");}
// 追記 - ログやストリームデータに最適function appendJsonl(filePath, record) { appendFileSync(filePath, JSON.stringify(record) + "\n", "utf-8");}JSONとJSONLの相互変換
import { readFileSync, writeFileSync } from "fs";
// JSON配列 → JSONLfunction jsonToJsonl(jsonPath, jsonlPath) { const data = JSON.parse(readFileSync(jsonPath, "utf-8")); const jsonl = data.map((item) => JSON.stringify(item)).join("\n") + "\n"; writeFileSync(jsonlPath, jsonl, "utf-8");}
// JSONL → JSON配列function jsonlToJson(jsonlPath, jsonPath) { const lines = readFileSync(jsonlPath, "utf-8") .split("\n") .filter((line) => line.trim()); const data = lines.map((line) => JSON.parse(line)); writeFileSync(jsonPath, JSON.stringify(data, null, 2), "utf-8");}まとめ
JSONとJSONLは競合するフォーマットではなく、それぞれ得意な領域が異なります。
- JSON: API通信、設定ファイル、小〜中規模の構造化データに最適
- JSONL: 大規模データ処理、ログ収集、機械学習パイプラインに最適
特にAI・機械学習の分野ではJSONLの利用が急速に広がっています。OpenAI APIのファインチューニングデータや、Hugging Faceのデータセットなど、身近なところで目にする機会も増えてきました。
扱うデータの規模やユースケースに応じて、適切なフォーマットを選択しましょう。
JSONLって1行ずつ処理できるから、めちゃくちゃ大きなファイルでもメモリを気にしなくていいんだよね!あたしも最初は「JSONと何が違うの?」って思ってたけど、ストリーム処理ができるのが本当に便利なんだ。さあ、試してみよう!