エラーの内容
json_decode() を呼び出すと、次のエラーが発生します:
json_decode() expects parameter 1 to be string, null given
PHP 8 の場合:
json_decode(): Argument #1 ($json) must be of type string, null given
関数が JSON 文字列の代わりに null を受け取っています。エラーが発生している行は明白ですが、本当の原因はその数行上に潜んでいます。
根本原因
- API レスポンスやファイル読み込みが
nullまたは空を返したにもかかわらず、チェックせずにそのままjson_decode()に渡している。これが最も一般的な原因です。 - 文字列を返すと思っていた関数が、失敗時に実は
nullを返している —file_get_contents()、$request->getContent()、DB カラムの読み取りはすべて暗黙的にこれを行います。 - 変数が一度も代入されていない — 変数名のタイポ、代入の漏れ、または条件分岐でスキップされたなど。
- JSON がユーザー入力や外部サービスから来ているが、何も送られてこなかった場合 — 空の POST ボディ、クエリパラメータの欠落、またはボディのない 4xx レスポンスなど。
修正1 — デコード前にガード処理を追加する
json_decode() に触れる前に、値が空でない文字列であることを確認します。シンプルな方法で、コードにおける「失敗」の意味を明確に決める必要があります。
<?php
$raw = file_get_contents('https://api.example.com/data');
if ($raw === false || $raw === null || $raw === '') {
// ログに記録する、例外をスローする、早期リターンする — どれか一つを選ぶ
throw new RuntimeException('Failed to fetch JSON data');
}
$data = json_decode($raw, true);
リクエストからのユーザー入力の場合:
<?php
$body = $request->getContent(); // 空文字列になる可能性がある
if (empty($body)) {
return response()->json(['error' => 'Empty request body'], 400);
}
$data = json_decode($body, true);
修正2 — デコード後に json_last_error() を確認する
注意すべき落とし穴があります: json_decode() が null を返すのは、まったく異なる2つの状況 — 入力が null だった場合と、かつ JSON が不正な形式だった場合 — のどちらでも起こります。確認しなければ、どちらが発生したか判断できません。
<?php
$data = json_decode($raw, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new RuntimeException('JSON decode failed: ' . json_last_error_msg());
}
これにより、無効な構文、予期しないエンコーディング、切り詰められたレスポンスなど、そのままでは静かに通過してデータを壊してしまう失敗を検出できます。
修正3 — オプションデータには Null 合体演算子を使う
値が存在しないことが完全に正常な場合もあります — nullable な DB カラムやオプションの設定フィールドなど。その場合は、処理を中断するのではなく、安全なフォールバックを提供します:
<?php
// $row['metadata'] が null の場合、代わりに空のオブジェクトをデコードする
$metadata = json_decode($row['metadata'] ?? '{}', true);
// 配列の場合
$tags = json_decode($row['tags'] ?? '[]', true);
データに対して実際に意味のあるフォールバックを選択してください。null に戻すフォールバックでは意味がありません。
修正4 — null の発生元を追跡する
原因が明らかでない場合は、デコード直前に値をダンプして実行を停止します:
<?php
var_dump($raw); // null か?false か?空文字列か?
die();
$data = json_decode($raw, true);
実際の値を確認すれば、通常は原因が明らかになります:
file_get_contents()がfalseを返した — パスが間違っている、権限が拒否されている、またはネットワークタイムアウト$_POST['json_data']が未定義 — フィールド名が間違っているか、クライアントがapplication/jsonではなくapplication/x-www-form-urlencodedを送信している- DB カラムが
NULL— 行は存在するが、そのフィールドが一度も入力されていない - cURL のレスポンスボディが空 — サーバーがボディなしで 4xx/5xx を返した
cURL の場合は HTTP ステータスコードも確認してください — 401 や 500 のレスポンスにはボディがないことが多いです:
<?php
$ch = curl_init('https://api.example.com/data');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($response === false || $httpCode !== 200) {
throw new RuntimeException("API request failed with HTTP $httpCode");
}
$data = json_decode($response, true);
修正5 — 一貫した処理のためにヘルパー関数でラップする
PHP 8 ではこれが警告からフルの TypeError に格上げされ、ログで発見しやすくなりました。いずれにせよ、複数の箇所で JSON をデコードしている場合は、処理を一元化しましょう:
<?php
function safe_json_decode(?string $json, bool $assoc = true): mixed
{
if ($json === null || $json === '') {
return null;
}
$data = json_decode($json, $assoc);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new \InvalidArgumentException('Invalid JSON: ' . json_last_error_msg());
}
return $data;
}
コードベース内のすべてのデコードが一つの関数を経由するようになります。エラーは、3層も深くに謎の null 値として伝播する代わりに、有用なメッセージと共に即座に表面化します。
修正の確認
スクラッチファイルで簡単なサニティチェックを行います:
<?php
// エラーなしで配列を返すはず
$result = json_decode('{"key": "value"}', true);
var_dump($result); // array(1) { ["key"] => string(5) "value" }
// null ガードにより空の配列が生成されるはず
$result = json_decode(null ?? '{}', true);
var_dump($result); // array(0) {}
Laravel や Symfony アプリでは、テストスイートを実行するか、エンドポイントに直接アクセスしてください:
curl -X POST https://yourapp.test/api/endpoint \
-H 'Content-Type: application/json' \
-d '{"test": true}'
異常系もテストしてください — 空のボディを送信し、クラッシュする代わりに適切な 400 が返ることを確認します。
予防策
本当の修正はデコード呼び出しの時点ではなく、その上流で行われます。API、ファイル、またはユーザー入力から来る JSON は、json_decode() に触れる前にバリデーションされるべきです。外部サービスは特に信頼性が低く、認証失敗時に空のボディを返したり、部分的なレスポンスで不正な JSON を返したり、何か問題が起きると普通の HTML エラーページを返したりします。
不正な JSON をデバッグする際は、ToolCraft の JSON Formatter & Validator をブックマークしておく価値があります — 生の文字列を貼り付けると、正確な構文エラーを即座に特定できます。すべてブラウザ内で動作し、何もアップロードされません。
このバグをほぼ不可能にする習慣:
- strict_types を有効にする(
declare(strict_types=1);)— PHP 8 では型が間違っている場合、json_decode()に到達する前にTypeErrorをスローします - 型付きクラスプロパティを使用する —
$this->jsonDataがstringとして宣言されている場合、nullの代入は3回の呼び出し後ではなく、代入時点で失敗します - 開発中はデコード前に生のレスポンスをログに記録する — 空または不正なレスポンスが現れた瞬間に検出できます
- 入力をデコードするすべてのエンドポイントに対して、
null、空文字列、破損した JSON を送るユニットテストを書く — エッジケースを明示的にする

