json_encodeのエラーについて調べた
var_dump くらいの気軽さで json_encode を使っていたが、エラー時の動作でハマったので調査した。
ハマった点
json_encode
に渡した array の中に、文字コードSJISの文字列が混じっていて失敗していた。
<?php
$sjis = mb_convert_encoding("あいうえお", "SJIS", "UTF-8");
var_dump(json_encode($sjis));
//=> bool(false)
json_encode
は正常に動作すると JSON形式の文字列が返るが、エラーがあった場合に WARNING がでたり例外発生したりはしない。
ただ false
を返すだけ。そのせいで特定に手間取った。
エラーの判定方法
json_encode
で不具合があったかどうかは、json_last_error
や json_last_error_msg
関数で判明する。エラーがない場合は json_last_error
は JSON_ERROR_NONE
(int:0) を返し、json_last_error_msg
は "No error"
を返す。
$sjis = mb_convert_encoding("あいうえお", "SJIS", "UTF-8");
var_dump(json_encode($sjis)); //=> bool(false)
// エラー情報があれば出力
if (json_last_error()) {
echo json_last_error() . "\n";
//=> 5
echo json_last_error_msg() . "\n";
//=> Malformed UTF-8 characters, possibly incorrectly encoded
}
または、json_encode
の第2引数に渡すビットフラグに JSON_THROW_ON_ERROR
がたっていれば例外が発生し、try/catchで捕捉できる(PHP7.3以降)
try {
$sjis = mb_convert_encoding("あいうえお", "SJIS", "UTF-8");
var_dump(json_encode($sjis, JSON_THROW_ON_ERROR));
} catch (Exception $e) {
echo get_class($e) . " - " . $e->getMessage() . "\n";
//=> JsonException - Malformed UTF-8 characters, possibly incorrectly encoded
}
ちなみに、入力値に複数のエラーがあった場合は、最初に発見したエラーで処理は中断されるので、json_last_error
で捕捉したエラーが全てとは限らない。
対処法
そもそも json_encode
でエラーが起きないようにデータを作成するのが一番だが、
それでもエラーが発生した際にどう対処するのが良いかを考える。
対処法1: UTF-8ではない文字列エラーを対策する
UTF-8ではない文字列エラー(JSON_ERROR_UTF8
)への対策ならば、JSON_INVALID_UTF8_SUBSTITUTE
や JSON_INVALID_UTF8_IGNORE
フラグを付けると問題箇所だけを除外・変換してくれる。
var_dump(json_encode("abc\x80def", JSON_INVALID_UTF8_SUBSTITUTE));
//=> string(14) ""abc\ufffddef""
var_dump(json_encode("abc\x80def", JSON_INVALID_UTF8_IGNORE));
//=> string(8) ""abcdef""
if (json_last_error()) {
// ここは実行されない
echo json_last_error() . ": " . json_last_error_msg() . "\n";
}
この場合、json_last_error
関数は JSON_ERROR_NONE
を返すのでエラーがあったのかどうか不明になる。想定外の出力をしていることに気付かないままになってしまいそうで、個人的には使わないかも。
対処法2: JsonExceptionを発生させる
判定方法でも挙げたが、JSON_THROW_ON_ERROR
フラグを使って、エラー時に例外発生させる。
この方法なら、全ての json_encode
エラーを捕捉できる。
$obj = array("\x80" => INF); //何らかのPHPオブジェクト
try {
$json = json_encode($obj, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
// エラー時処理
echo "json_encode でエラー: " . $e->getMessage() . "\n";
var_dump($obj);
}
例外を catch してからどうするかは、場合によるだろう。強制終了させるなり、エラーログに吐き出すなりご自由に
対処法3: エラーは捕捉しつつそれなりの出力する
JSON_PARTIAL_OUTPUT_ON_ERROR
フラグを利用すると、変換できなかった部分だけ null
, 0
, ""
等に置換してくれる。
$obj = array("\x80" => INF); //何らかのPHPオブジェクト
var_dump(json_encode($obj, JSON_PARTIAL_OUTPUT_ON_ERROR));
//=> string(6) "{"":0}"
if (json_last_error()) {
echo json_last_error() . ": " . json_last_error_msg() . "\n";
//=> 7: Inf and NaN cannot be JSON encoded
}
この方法では、json_last_error
でエラーがわかるし、json_encode
の返値もあるので、問題がなかった部分は利用できる。出力値があるのでエラー箇所の特定をしたい場合にも便利かも。
対処法まとめ
絶対の正解はないが、個人的には対処法2か3を選択するのがよいか。
参考: json_encode エラーの種類
今回は文字コードのせいであったが、その他にどういうケースがあるのかも調べた。また実際にエラーが起きるコードを記述した
JSON_ERROR_DEPTH
ネストが深すぎる配列などをエンコードしようとすると発生する
// 512階層
$depth512 = array(1);
for($i=0; $i<512; $i++) {$depth512 = array($depth512);}
json_encode($depth512);
echo json_last_error() . ": " . json_last_error_msg() . "\n";
//=> 1: Maximum stack depth exceeded
JSON_ERROR_UTF8
UTF-8ではあり得ないバイト列が含まれると発生
json_encode("\x80");
echo json_last_error() . ": " . json_last_error_msg() . "\n";
//=> 5: Malformed UTF-8 characters, possibly incorrectly encoded
JSON_ERROR_RECURSION
循環参照を含んでいると発生する
// 循環参照
$rec = array(1,2,3);
$rec[] = &$rec;
json_encode($rec);
echo json_last_error() . ": " . json_last_error_msg() . "\n";
//=> 6: Recursion detected
JSON_ERROR_INF_OR_NAN
INF
, NAN
を含んでいる
// Inf, NaN
json_encode(array(INF, NAN));
echo json_last_error() . ": " . json_last_error_msg() . "\n";
//=> 7: Inf and NaN cannot be JSON encoded
JSON_ERROR_UNSUPPORTED_TYPE
リソース型を含んでいる
// Resource
$fh = fopen(__FILE__, "r");
json_encode($fh);
fclose($fh);
echo json_last_error() . ": " . json_last_error_msg() . "\n";
//=> 8: Type is not supported
JSON_ERROR_NON_BACKED_ENUM
ドキュメントにはないが、値依存ではないEnum型もエラーになる
// Enum(値依存ではない)
enum En {
case X;
}
json_encode(En::X);
echo json_last_error() . ": " . json_last_error_msg() . "\n";
//=> 11: Non-backed enums have no default serialization