はじめに
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開発において必須のスキルですので、ぜひ実践で活用してください。