[PHP]json_last_error関数徹底解説 – JSONエラーを完璧にハンドリングする方法

PHP

はじめに

PHPでJSON処理を行う際、json_decode()やjson_encode()関数でエラーが発生することがあります。しかし、これらの関数は直接的なエラーメッセージを返さないため、問題の原因を特定するのが困難な場合があります。

そこで重要になるのが「json_last_error」関数です。この関数を使用することで、JSON処理で発生したエラーの詳細を把握し、適切な対処を行うことができます。

この記事では、json_last_error関数の使い方から実践的なエラーハンドリング手法まで、詳しく解説していきます。

json_last_error関数とは?

json_last_error関数は、最後に実行されたJSON操作(json_decode、json_encodeなど)で発生したエラーコードを返す関数です。エラーが発生していない場合はJSON_ERROR_NONEを返します。

基本的な構文

json_last_error(): int

パラメータはなく、戻り値として整数のエラーコードを返します。

エラーコードの種類と意味

主要なエラーコード一覧

// エラーなし
JSON_ERROR_NONE = 0

// 最大スタック深度を超過
JSON_ERROR_DEPTH = 1

// JSON の状態が一致しない、または無効
JSON_ERROR_STATE_MISMATCH = 2

// 制御文字エラー
JSON_ERROR_CTRL_CHAR = 3

// 構文エラー、形式が正しくない JSON
JSON_ERROR_SYNTAX = 4

// UTF-8 エラー、文字エンコードが正しくない
JSON_ERROR_UTF8 = 5

// 再帰的な参照
JSON_ERROR_RECURSION = 6

// NAN または INF の値
JSON_ERROR_INF_OR_NAN = 7

// サポートされていない型
JSON_ERROR_UNSUPPORTED_TYPE = 8

// 無効なプロパティ名
JSON_ERROR_INVALID_PROPERTY_NAME = 9

// UTF-16 サロゲートが不正
JSON_ERROR_UTF16 = 10

json_last_error_msg関数との組み合わせ

PHP 5.5.0以降では、json_last_error_msg()関数も利用できます。これはエラーメッセージを文字列で返します。

$invalidJson = '{"name": "田中", "age": 30,}'; // 末尾のカンマが不正

$result = json_decode($invalidJson);

if (json_last_error() !== JSON_ERROR_NONE) {
    echo "エラーコード: " . json_last_error() . "\n";
    echo "エラーメッセージ: " . json_last_error_msg() . "\n";
}

// 出力:
// エラーコード: 4
// エラーメッセージ: Syntax error

実践的なエラーハンドリング

基本的なエラーチェック関数

function checkJsonError($operation = 'JSON操作') {
    $error = json_last_error();
    
    if ($error === JSON_ERROR_NONE) {
        return true; // エラーなし
    }
    
    $errorMessages = [
        JSON_ERROR_DEPTH => '最大スタック深度を超過しました',
        JSON_ERROR_STATE_MISMATCH => 'JSONの状態が一致しません(無効またはフォーマット不正)',
        JSON_ERROR_CTRL_CHAR => '制御文字エラー(エンコーディングが正しくない可能性があります)',
        JSON_ERROR_SYNTAX => '構文エラー(JSONフォーマットが正しくありません)',
        JSON_ERROR_UTF8 => 'UTF-8文字が正しくありません(エンコーディングエラー)',
        JSON_ERROR_RECURSION => '再帰的な参照が検出されました',
        JSON_ERROR_INF_OR_NAN => 'NANまたはINFの値が含まれています',
        JSON_ERROR_UNSUPPORTED_TYPE => 'サポートされていないデータ型です',
        JSON_ERROR_INVALID_PROPERTY_NAME => '無効なプロパティ名です',
        JSON_ERROR_UTF16 => 'UTF-16サロゲートが正しくありません'
    ];
    
    $message = $errorMessages[$error] ?? '不明なエラーが発生しました';
    
    throw new JsonException("{$operation}でエラーが発生しました: {$message} (エラーコード: {$error})");
}

json_decode用の安全な関数

function safeJsonDecode($json, $assoc = false, $depth = 512, $flags = 0) {
    // 入力値の事前チェック
    if (!is_string($json)) {
        throw new InvalidArgumentException('JSONデータは文字列である必要があります');
    }
    
    if (empty($json)) {
        throw new InvalidArgumentException('JSONデータが空です');
    }
    
    // JSON デコード実行
    $result = json_decode($json, $assoc, $depth, $flags);
    
    // エラーチェック
    $error = json_last_error();
    
    if ($error !== JSON_ERROR_NONE) {
        $errorDetails = [
            'input' => mb_substr($json, 0, 100) . (mb_strlen($json) > 100 ? '...' : ''),
            'error_code' => $error,
            'error_message' => json_last_error_msg(),
            'parameters' => [
                'assoc' => $assoc,
                'depth' => $depth,
                'flags' => $flags
            ]
        ];
        
        throw new JsonException('JSON デコードに失敗しました: ' . json_encode($errorDetails, JSON_UNESCAPED_UNICODE));
    }
    
    return $result;
}

// 使用例
try {
    $validJson = '{"name": "田中太郎", "age": 30}';
    $data = safeJsonDecode($validJson, true);
    echo "成功: " . print_r($data, true);
    
    $invalidJson = '{"name": "田中", "age":}'; // 値が不正
    $data = safeJsonDecode($invalidJson, true);
    
} catch (JsonException $e) {
    echo "エラー: " . $e->getMessage() . "\n";
} catch (InvalidArgumentException $e) {
    echo "引数エラー: " . $e->getMessage() . "\n";
}

json_encode用の安全な関数

function safeJsonEncode($data, $flags = 0, $depth = 512) {
    // データの事前チェック
    if ($data === null) {
        return 'null';
    }
    
    // JSON エンコード実行
    $result = json_encode($data, $flags, $depth);
    
    // エラーチェック
    $error = json_last_error();
    
    if ($error !== JSON_ERROR_NONE) {
        $errorInfo = [
            'data_type' => gettype($data),
            'error_code' => $error,
            'error_message' => json_last_error_msg(),
            'flags' => $flags,
            'depth' => $depth
        ];
        
        // データ型別の詳細情報
        if (is_array($data)) {
            $errorInfo['array_info'] = [
                'count' => count($data),
                'is_assoc' => array_keys($data) !== range(0, count($data) - 1)
            ];
        } elseif (is_object($data)) {
            $errorInfo['object_info'] = [
                'class' => get_class($data),
                'properties' => array_keys(get_object_vars($data))
            ];
        }
        
        throw new JsonException('JSON エンコードに失敗しました: ' . print_r($errorInfo, true));
    }
    
    if ($result === false) {
        throw new JsonException('JSONエンコードが予期せず失敗しました');
    }
    
    return $result;
}

// 使用例
try {
    $data = ['name' => '田中', 'age' => 30];
    $json = safeJsonEncode($data, JSON_UNESCAPED_UNICODE);
    echo "成功: " . $json . "\n";
    
    // 無限ループを含むデータ(エラーの例)
    $a = ['test' => null];
    $b = ['ref' => &$a];
    $a['test'] = &$b;
    
    $json = safeJsonEncode($a);
    
} catch (JsonException $e) {
    echo "エラー: " . $e->getMessage() . "\n";
}

特定のエラーに対する対処法

JSON_ERROR_SYNTAX(構文エラー)の対処

function fixCommonJsonSyntaxErrors($json) {
    // よくある構文エラーを自動修正
    $fixes = [
        // 末尾のカンマを削除
        '/,(\s*[}\]])/' => '$1',
        // シングルクォートをダブルクォートに変換
        "/'/", '"',
        // プロパティ名をクォートで囲む
        '/([{,]\s*)([a-zA-Z_][a-zA-Z0-9_]*)\s*:/' => '$1"$2":',
    ];
    
    $fixedJson = $json;
    foreach ($fixes as $pattern => $replacement) {
        $fixedJson = preg_replace($pattern, $replacement, $fixedJson);
    }
    
    return $fixedJson;
}

function attemptJsonDecode($json, $assoc = false) {
    // 最初に通常のデコードを試行
    $result = json_decode($json, $assoc);
    
    if (json_last_error() === JSON_ERROR_NONE) {
        return $result;
    }
    
    // 構文エラーの場合、修正を試行
    if (json_last_error() === JSON_ERROR_SYNTAX) {
        echo "構文エラーを検出しました。自動修正を試行します...\n";
        
        $fixedJson = fixCommonJsonSyntaxErrors($json);
        $result = json_decode($fixedJson, $assoc);
        
        if (json_last_error() === JSON_ERROR_NONE) {
            echo "自動修正に成功しました\n";
            return $result;
        }
    }
    
    // 修正に失敗した場合はエラーを投げる
    checkJsonError('JSON解析');
}

// 使用例
$brokenJson = "{'name': '田中', 'age': 30,}"; // 様々な構文エラー

try {
    $data = attemptJsonDecode($brokenJson, true);
    print_r($data);
} catch (JsonException $e) {
    echo "修正できませんでした: " . $e->getMessage() . "\n";
}

JSON_ERROR_UTF8(文字エンコーディング)の対処

function handleUtf8JsonError($data, $flags = 0) {
    // 最初に通常のエンコードを試行
    $result = json_encode($data, $flags);
    
    if (json_last_error() !== JSON_ERROR_UTF8) {
        return $result;
    }
    
    echo "UTF-8エラーを検出しました。文字エンコーディングを修正します...\n";
    
    // 再帰的にUTF-8エンコーディングを修正
    $fixedData = fixUtf8Recursively($data);
    
    $result = json_encode($fixedData, $flags);
    
    if (json_last_error() === JSON_ERROR_NONE) {
        echo "文字エンコーディングの修正に成功しました\n";
        return $result;
    }
    
    throw new JsonException('UTF-8エンコーディングエラーを修正できませんでした');
}

function fixUtf8Recursively($data) {
    if (is_string($data)) {
        // 文字エンコーディングをUTF-8に変換
        $encoding = mb_detect_encoding($data, ['UTF-8', 'SJIS', 'EUC-JP', 'ASCII'], true);
        if ($encoding && $encoding !== 'UTF-8') {
            return mb_convert_encoding($data, 'UTF-8', $encoding);
        }
        
        // 無効なUTF-8文字を修正
        return mb_convert_encoding($data, 'UTF-8', 'UTF-8');
    }
    
    if (is_array($data)) {
        return array_map('fixUtf8Recursively', $data);
    }
    
    if (is_object($data)) {
        $fixedData = new stdClass();
        foreach ($data as $key => $value) {
            $fixedKey = fixUtf8Recursively($key);
            $fixedData->$fixedKey = fixUtf8Recursively($value);
        }
        return $fixedData;
    }
    
    return $data;
}

// 使用例
$dataWithEncodingIssues = [
    'name' => mb_convert_encoding('田中太郎', 'SJIS', 'UTF-8'), // SJIS文字列
    'message' => 'こんにちは' // 正常なUTF-8
];

try {
    $json = handleUtf8JsonError($dataWithEncodingIssues, JSON_UNESCAPED_UNICODE);
    echo "成功: " . $json . "\n";
} catch (JsonException $e) {
    echo "エラー: " . $e->getMessage() . "\n";
}

Web API でのエラーハンドリング

APIレスポンス用のエラーハンドラー

class ApiResponseHandler {
    
    public static function createErrorResponse($message, $code = 400, $details = null) {
        $response = [
            'success' => false,
            'error' => [
                'message' => $message,
                'code' => $code,
                'timestamp' => date('c')
            ]
        ];
        
        if ($details !== null) {
            $response['error']['details'] = $details;
        }
        
        return self::safeJsonResponse($response, $code);
    }
    
    public static function createSuccessResponse($data, $message = null) {
        $response = [
            'success' => true,
            'data' => $data
        ];
        
        if ($message !== null) {
            $response['message'] = $message;
        }
        
        return self::safeJsonResponse($response, 200);
    }
    
    private static function safeJsonResponse($data, $httpCode = 200) {
        header('Content-Type: application/json; charset=utf-8');
        http_response_code($httpCode);
        
        try {
            $json = safeJsonEncode($data, JSON_UNESCAPED_UNICODE);
            echo $json;
        } catch (JsonException $e) {
            // JSONエンコードに失敗した場合の緊急対応
            http_response_code(500);
            echo json_encode([
                'success' => false,
                'error' => [
                    'message' => 'レスポンスの生成に失敗しました',
                    'details' => 'JSON encoding error: ' . $e->getMessage()
                ]
            ]);
        }
    }
}

// 使用例
try {
    // 何らかの処理
    $userData = ['name' => '田中太郎', 'age' => 30];
    ApiResponseHandler::createSuccessResponse($userData, 'ユーザー情報を取得しました');
    
} catch (Exception $e) {
    ApiResponseHandler::createErrorResponse(
        'サーバーエラーが発生しました',
        500,
        ['exception' => $e->getMessage()]
    );
}

ログ出力とデバッグ

エラー情報の詳細ログ

class JsonErrorLogger {
    
    public static function logJsonError($context, $data = null, $json = null) {
        $error = json_last_error();
        
        if ($error === JSON_ERROR_NONE) {
            return; // エラーなし
        }
        
        $logData = [
            'timestamp' => date('c'),
            'context' => $context,
            'error_code' => $error,
            'error_message' => json_last_error_msg(),
            'php_version' => PHP_VERSION,
            'memory_usage' => memory_get_usage(true),
            'backtrace' => debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 5)
        ];
        
        // データが提供されている場合、安全に情報を追加
        if ($data !== null) {
            $logData['data_info'] = [
                'type' => gettype($data),
                'size' => is_string($data) ? strlen($data) : (is_array($data) ? count($data) : 'unknown')
            ];
            
            // 小さなデータの場合は内容も記録
            if (is_string($data) && strlen($data) < 1000) {
                $logData['data_sample'] = mb_substr($data, 0, 200);
            }
        }
        
        if ($json !== null && is_string($json)) {
            $logData['json_sample'] = mb_substr($json, 0, 200);
        }
        
        // ログファイルに出力
        $logMessage = json_encode($logData, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
        error_log("JSON Error: " . $logMessage, 3, 'json_errors.log');
        
        // 開発環境では詳細を表示
        if (defined('DEBUG') && DEBUG) {
            echo "<pre>JSON Error Details:\n" . $logMessage . "</pre>";
        }
    }
}

// 使用例
$problematicData = ['test' => "\x80\x81"]; // 無効なUTF-8

$json = json_encode($problematicData);
JsonErrorLogger::logJsonError('ユーザーデータのエンコード処理', $problematicData);

まとめ

json_last_error関数は、PHPでJSON処理を行う際の重要なデバッグツールです。適切なエラーハンドリングを実装することで、以下のメリットが得られます:

主要なポイント:

  • エラーの早期発見と原因特定が可能
  • ユーザーフレンドリーなエラーメッセージの提供
  • システムの安定性向上
  • デバッグ効率の大幅な改善
  • APIの信頼性向上

ベストプラクティス:

  • 必ずjson_last_error()でエラーチェックを行う
  • json_last_error_msg()と組み合わせて詳細な情報を取得
  • エラーの種類に応じた適切な対処法を実装
  • ログ出力でトラブルシューティングを効率化
  • 本番環境では適切なエラーハンドリングでセキュリティを確保

これらのテクニックを活用することで、より堅牢で保守性の高いPHPアプリケーションを開発できるようになります。JSON処理は現代のWeb開発において必須のスキルですので、ぜひ実践で活用してください。

タイトルとURLをコピーしました