背景午前2時、ターミナルを見つめているあなたの前に、1行のメッセージが立ちはだかります:SyntaxError: Unexpected end of JSON input。このエラーはNode.js開発における「通過儀礼」のようなものです。通常、APIレスポンスの処理、ローカル設定ファイルの読み込み、あるいはサービス間でのデータストリーミングを行っている際に発生します。
簡単に言えば、このエラーは JSON.parse() が文字列の読み込みを開始し、有効な構造を完成させるためにより多くのデータを期待していたものの、途中で文字列が途切れてしまったことを意味します。食事の途中でウェイターに皿を下げられてしまうようなものです。通常、原因はJSON内のタイポ(打ち間違い)ではなく、データが空の文字列、undefined、あるいはネットワークパケットの欠落である場合がほとんどです。
デバッグのプロセスカンマの付け忘れを探すのはやめましょう。このエラーに直面した際、多くの開発者が最初に犯す間違いは、JSONの「構造」を精査することです。それは後回しにしてください。代わりに、データの「存在」を確認しましょう。
1. 生の入力をログに出力するJSON.parse() や res.json() を呼び出す前に、生のデータを検査してください。Fetch APIを使用している場合は、すぐに .json() に進まず、まずは .text() を使用して、実際にどのような文字が送られてきているかを確認します。
// fetch呼び出しのデバッグ
const response = await fetch('https://api.example.com/data');
const rawText = await response.text();
console.log('Raw output:', rawText);
console.log('Byte length:', rawText.length);
// コンテンツが存在する場合のみパースする
const data = rawText ? JSON.parse(rawText) : {};
もし rawText が空の文字列("")であれば、JSON.parse() は100%失敗します。0バイトのレスポンスは、この厄介な問題の最も一般的な原因です。
2. HTTPステータスコードを確認する204 No Content や 404 Not Found レスポンスは、多くの場合、完全に空のボディを返します。ロジックが、有効なペイロードを持つ 200 OK の成功を前提としている場合、その空の状態をパースしようとした瞬間にコードはクラッシュします。
一般的なシナリオと解決策### シナリオA:空のAPIレスポンス多くの現代的なREST APIは、DELETE や PUT リクエストに対して空のボディを返します。削除結果をパースしようとすると、エラーが発生する可能性が高くなります。
const res = await fetch(url, { method: 'DELETE' });
// サーバーが 204 No Content を返すと、この行でクラッシュします
const body = await res.json();
修正方法: パースを試みる前に、ステータスコードまたはコンテンツの長さを確認します。
const res = await fetch(url, { method: 'DELETE' });
let data = {};
if (res.status !== 204 && res.ok) {
data = await res.json();
}
シナリオB:0バイトのファイルの読み込みfs.readFileSync を使って設定ファイルを読み込んでいますか?もしそのファイルが空であれば(以前の書き込み操作の失敗などが原因で)、パースに失敗します。
const fs = require('fs');
// config.json が 0バイトの場合、エラーが発生します
const config = JSON.parse(fs.readFileSync('./config.json', 'utf8'));
修正方法: パーサーに渡す前に、文字列に内容があることを検証します。
const rawConfig = fs.readFileSync('./config.json', 'utf8');
const config = rawConfig.trim() ? JSON.parse(rawConfig) : {};
シナリオC:途切れたストリームNode.jsのストリームからデータチャンクを収集している際、end イベントが発生する前にデータをパースしてしまっている可能性があります。あるいは、ネットワーク接続が転送途中で切れ、{"user": "admin", "status": のように中途半端なJSONオブジェクトが残ってしまうこともあります。
let body = '';
req.on('data', chunk => {
body += chunk;
});
req.on('end', () => {
try {
const data = JSON.parse(body);
} catch (e) {
console.error("不完全なJSONを受信しました。接続が切断された可能性があります。");
}
});
検証ステップ修正が実際に機能することを確認するために、失敗をシミュレートしてください。Postmanのようなツールを使用して、0バイトのボディを持つ 200 OK レスポンスをモックすることができます。アプリケーションがクラッシュせずにこれに耐えられれば、準備は万端です。
パース関数に空の文字列や不完全なJSON文字列({"id": 1 など)を渡すユニットテストを作成しましょう。汎用的な SyntaxError を発生させるのではなく、デフォルト値を返すか、役立つカスタムエラーを返すべきです。
予防とベストプラクティス常に JSON.parse() を try-catch ブロックで囲んでください。JSONは外部データであり、決して信頼してはいけません。APIドキュメントが完璧なオブジェクトを約束していても関係ありません。ネットワークは不安定なものです。
失敗し続ける大規模なペイロードを扱っている場合は、生の出力をバリデーターにコピーしてみてください。私は ToolCraft の JSON Formatter & Validator を使用しています。隠れた非表示文字を見つけたり、ペイロードがどこで途切れたかを正確に把握したりするのに便利です。ブラウザ上で動作するため、機密性の高いプロダクションログを不特定のサーバーに送信するリスクもありません。

