← ブログに戻る

2026年に大きなJSONファイルをクラッシュせずにパースする方法

JavaScriptとPythonで大きなJSONファイル(100MB以上)を扱うための完全ガイド。メモリエラーを回避するためのストリーミング、プログレッシブパース、チャンキング技術を学びます。

Big JSON Team15 分で読めますprogramming
B

Big JSON Team

Technical Writer

Expert in JSON data manipulation, API development, and web technologies. Passionate about creating tools that make developers' lives easier.

15 分読む

# 2026年に大きなJSONファイルをクラッシュせずにパースする方法

はじめに

大きなJSONファイルをパースしようとして「FATAL ERROR: JavaScript heap out of memory」エラーに遭遇したことはありませんか?あなただけではありません。大きなJSONファイル(100MB以上)の処理は、現代のWeb開発で最も一般的な課題の1つです。

この包括的なガイドでは、メモリを使い果たしたりアプリケーションをクラッシュさせることなく、大きなJSONファイルを効率的にパースするための実証済みの技術を学びます。

なぜJSON.parse()は大きなファイルで失敗するのか

標準のJSON.parse()メソッドは、ファイル全体を一度にメモリにロードします:

// ⚠️ 大きなファイル(>100MB)には悪い

const fs = require('fs');

const data = fs.readFileSync('huge-file.json', 'utf8');

const json = JSON.parse(data); // 大量のメモリを消費!

なぜ失敗するのか:
  • 文字列全体をメモリに保存(ファイルサイズの2-3倍)
  • 完全なJavaScriptオブジェクトを作成(さらにファイルサイズの2-5倍)
  • 完了するまでメモリの解放がない
  • 合計: 500MBのファイルが2GB以上のメモリを使用する可能性があります!

解決策1: JSONStreamによるストリーミング (Node.js)

大きなJSONファイルに対する最良のアプローチはストリーミングです - データを少しずつ処理します。

実践的な例

const fs = require('fs');

const JSONStream = require('JSONStream');

// オブジェクトの配列をパース

const stream = fs.createReadStream('large-dataset.json', { encoding: 'utf8' });

const parser = JSONStream.parse(''); // 配列要素には''

let count = 0;

parser.on('data', (item) => {

// 一度に1つのアイテムを処理

console.log(レコード${++count}を処理中:, item.id);

// ここにロジック - DBに挿入、変換など

if (item.price > 1000) {

saveToDatabase(item);

}

});

parser.on('end', () => {

console.log(${count}レコードを正常に処理しました);

});

parser.on('error', (err) => {

console.error('パースエラー:', err);

});

stream.pipe(parser);

ネストされたJSONパスのパース

// 用途: { "users": [ {...}, {...} ] }

const parser = JSONStream.parse('users.');

// 用途: { "data": { "items": [...] } }

const parser = JSONStream.parse('data.items.');

// 用途: 深い構造や複雑な条件

const parser = JSONStream.parse(['data', 'nested', { emitKey: true }]);

パフォーマンス比較

| メソッド | ファイルサイズ | メモリ使用量 | 時間 |

|---------|-------------|------------|-----|

| JSON.parse() | 500MB | ~2.1GB 💥 | 8秒 |

| JSONStream | 500MB | ~45MB ✅ | 12秒 |

| stream-json | 500MB | ~38MB ✅ | 10秒 |

ベンチマーク: Node.js 20, Mac M1, 16GB RAM

解決策2: stream-json (より高速)

stream-jsonはJSONStreamより高速で、より多くの制御を提供します:
const { chain } = require('stream-chain');

const { parser } = require('stream-json');

const { streamArray } = require('stream-json/streamers/StreamArray');

const pipeline = chain([

fs.createReadStream('huge-array.json'),

parser(),

streamArray()

]);

pipeline.on('data', ({ key, value }) => {

console.log(インデックス${key}:, value.name);

// ここで処理

processRecord(value);

});

pipeline.on('end', () => console.log('完了!'));

JSONオブジェクト用(配列以外)

const { streamObject } = require('stream-json/streamers/StreamObject');

const pipeline = chain([

fs.createReadStream('huge-object.json'),

parser(),

streamObject()

]);

pipeline.on('data', ({ key, value }) => {

console.log(キー: ${key}, value);

});

解決策3: チャンク読み込み (ブラウザ)

ブラウザでは、FileReaderを使用してファイルを段階的に処理します:

async function parseHugeJSON(file) {

const chunkSize = 1024 1024; // 1MB

let offset = 0;

let buffer = '';

let results = [];

while (offset < file.size) {

const chunk = file.slice(offset, offset + chunkSize);

const text = await chunk.text();

buffer += text;

// 完全なオブジェクトをパースしようと試みる

const lines = buffer.split('\n');

buffer = lines.pop(); // 不完全な行を保持

for (const line of lines) {

if (line.trim()) {

try {

const obj = JSON.parse(line);

results.push(obj);

// メモリビルドアップを避けるためにバッチ処理

if (results.length >= 1000) {

await processBatch(results);

results = [];

}

} catch (e) {

console.warn('無効な行をスキップ:', line.substring(0, 50));

}

}

}

offset += chunkSize;

// 進捗を報告

const progress = (offset / file.size 100).toFixed(1);

console.log(進捗: ${progress}%);

}

// 最後のバッチを処理

if (results.length > 0) {

await processBatch(results);

}

}

// 使用方法

document.getElementById('fileInput').addEventListener('change', async (e) => {

const file = e.target.files[0];

await parseHugeJSON(file);

});

解決策4: Web Workers (ノンブロッキング)

UIの凍結を防ぐため、パースをWeb Workerに移動します:

// worker.js

self.onmessage = async function(e) {

const { file } = e.data;

const chunkSize = 1024 1024;

let processed = 0;

for (let offset = 0; offset < file.size; offset += chunkSize) {

const chunk = file.slice(offset, offset + chunkSize);

const text = await chunk.text();

// チャンクを処理

const lines = text.split('\n').filter(l => l.trim());

const objects = lines.map(line => {

try {

return JSON.parse(line);

} catch {

return null;

}

}).filter(Boolean);

// 結果を送り返す

self.postMessage({

type: 'progress',

data: objects,

percent: (offset / file.size 100)

});

processed += objects.length;

}

self.postMessage({ type: 'complete', total: processed });

};

// main.js

const worker = new Worker('worker.js');

worker.onmessage = function(e) {

if (e.data.type === 'progress') {

console.log(処理済み: ${e.data.percent.toFixed(1)}%);

updateUI(e.data.data);

} else if (e.data.type === 'complete') {

console.log(完了! 合計: ${e.data.total});

}

};

// 処理を開始

const fileInput = document.getElementById('upload');

fileInput.addEventListener('change', (e) => {

worker.postMessage({ file: e.target.files[0] });

});

解決策5: APIのページネーション

APIからJSONを取得する場合は、ページネーションを使用します:

async function fetchAllData(apiUrl) {

let allData = [];

let page = 1;

let hasMore = true;

while (hasMore) {

const response = await fetch(${apiUrl}?page=${page}&limit=100);

const data = await response.json();

// ページをすぐに処理

await processPage(data.items);

allData.push(...data.items);

hasMore = data.hasNextPage;

page++;

// レート制限を避けるために遅延を追加

await new Promise(resolve => setTimeout(resolve, 100));

}

return allData;

}

// 最適化版: すべてを保存しない

async function processAllData(apiUrl) {

let page = 1;

let hasMore = true;

let totalProcessed = 0;

while (hasMore) {

const response = await fetch(${apiUrl}?page=${page}&limit=100);

const data = await response.json();

// 保存せずに処理

for (const item of data.items) {

await processItem(item); // DBに保存、変換など

totalProcessed++;

}

hasMore = data.hasNextPage;

page++;

console.log(これまでに${totalProcessed}アイテムを処理...);

}

return totalProcessed;

}

解決策6: ijsonを使用したPython

サーバーサイド処理には、Pythonのijsonが優れています:

import ijson

def process_large_json(filename):

with open(filename, 'rb') as file:

# オブジェクトの配列をパース

objects = ijson.items(file, 'item')

count = 0

for obj in objects:

# 各要素を処理

if obj.get('price', 0) > 1000:

save_to_database(obj)

count += 1

if count % 1000 == 0:

print(f"{count}レコードを処理しました...")

print(f"合計: {count}レコード")

# ネストされた構造用

def process_nested(filename):

with open(filename, 'rb') as file:

# 特定のキーのみを抽出

for user_id in ijson.items(file, 'users.item.id'):

print(f"ユーザーID: {user_id}")

最適化戦略

1. 個別ではなくバッチ処理

let batch = [];

const BATCH_SIZE = 1000;

parser.on('data', async (item) => {

batch.push(item);

if (batch.length >= BATCH_SIZE) {

await insertBatch(batch); // 1回のバッチ挿入

batch = [];

}

});

parser.on('end', async () => {

if (batch.length > 0) {

await insertBatch(batch); // 最後のバッチを処理

}

});

2. 早期データフィルタリング

const { pick } = require('stream-json/filters/Pick');

const { streamArray } = require('stream-json/streamers/StreamArray');

const pipeline = chain([

fs.createReadStream('data.json'),

parser(),

pick({ filter: 'users' }), // "users"プロパティのみ

streamArray()

]);

3. ファイル圧縮

const zlib = require('zlib');

const pipeline = chain([

fs.createReadStream('data.json.gz'),

zlib.createGunzip(), // オンザフライで解凍

parser(),

streamArray()

]);

一般的なシナリオと解決策

シナリオ1: JSONからのデータベースインポート

const { chain } = require('stream-chain');

const { parser } = require('stream-json');

const { streamArray } = require('stream-json/streamers/StreamArray');

const { MongoClient } = require('mongodb');

async function importToMongo(filename, collectionName) {

const client = await MongoClient.connect('mongodb://localhost:27017');

const collection = client.db('mydb').collection(collectionName);

let batch = [];

const BATCH_SIZE = 1000;

const pipeline = chain([

fs.createReadStream(filename),

parser(),

streamArray()

]);

for await (const { value } of pipeline) {

batch.push(value);

if (batch.length >= BATCH_SIZE) {

await collection.insertMany(batch);

console.log(${batch.length}ドキュメントを挿入);

batch = [];

}

}

if (batch.length > 0) {

await collection.insertMany(batch);

}

await client.close();

}

シナリオ2: JSONをCSVに変換

const { Transform } = require('stream');

const csvWriter = require('csv-write-stream');

const jsonToCSV = new Transform({

objectMode: true,

transform(chunk, encoding, callback) {

// JSONオブジェクトをCSV行に変換

callback(null, {

id: chunk.id,

name: chunk.name,

email: chunk.email

});

}

});

const writer = csvWriter();

writer.pipe(fs.createWriteStream('output.csv'));

pipeline

.pipe(jsonToCSV)

.pipe(writer);

シナリオ3: 改行区切りJSON (NDJSON)の処理

const readline = require('readline');

async function processNDJSON(filename) {

const fileStream = fs.createReadStream(filename);

const rl = readline.createInterface({

input: fileStream,

crlfDelay: Infinity

});

let count = 0;

for await (const line of rl) {

if (line.trim()) {

const obj = JSON.parse(line);

await processObject(obj);

count++;

}

}

console.log(${count}行を処理しました);

}

トラブルシューティング

エラー: JavaScript heap out of memory

解決策:
# Node.jsでメモリ制限を増やす

node --max-old-space-size=4096 script.js

しかし、もっと良いのは: 代わりにストリーミングを使用してください!

エラー: チャンク読み込みで無効なJSON

問題: チャンクがJSONオブジェクトを分割する可能性があります。 解決策: 行区切りJSON (NDJSON)を使用します:
// まずJSONをNDJSONに変換

const items = require('./data.json'); // 一度だけ小さく読み込む

const stream = fs.createWriteStream('data.ndjson');

items.forEach(item => {

stream.write(JSON.stringify(item) + '\n');

});

stream.end();

エラー: 処理が遅すぎる

最適化:
  • JSONStreamの代わりにstream-jsonを使用(2倍速い)
  • バッチサイズを増やす(1000 → 5000)
  • DB挿入を並列化
  • DBコネクションプールを使用
  • 一括挿入前にインデックスを追加
  • 推奨ツール

    JSON検証用

    BigJSON.onlineを使用して、完全にロードせずに大きなJSONファイルを迅速に検証および検査します。ストリーミングとリアルタイム検証をサポートしています。

    ライブラリ

    • Node.js: stream-json (最速)、JSONStream (最もシンプル)
    • Python: ijson (最高)
    • ブラウザ: FileReader API + Web Workers

    まとめ

    クイック推奨事項:

    | ユースケース | 最適な解決策 | メモリ使用量 |

    |----------|-----------|-----------|

    | Node.js (大きな配列) | stream-json | 非常に低い |

    | Node.js (単純な構造) | JSONStream | 低い |

    | ブラウザ (< 50MB) | チャンク読み込み | 中程度 |

    | ブラウザ (> 50MB) | Web Workers | 低い |

    | APIダウンロード | ページネーション + ストリーム処理 | 非常に低い |

    | Python | ijson | 非常に低い |

    重要なポイント:
    • 50MB以上のファイルには常にストリーミングを使用
    • DB挿入にはバッチ処理
    • メモリ使用量を減らすために早期フィルタリング
    • 大きなファイルにはJSON.parse()を使用しない
    • すべてを一度にメモリにロードしない

    最新のストリーミングパッケージを使用すれば、メモリを使い果たしたりクラッシュすることなく、GB サイズのJSONファイルを簡単に処理できます。

    その他のリソース

    このガイドは役に立ちましたか?大きなJSONファイルに苦労している開発者仲間と共有してください!

    Share:

    関連記事

    Read in English