LoginSignup
1
0

json_encodeのエラーについて調べた

Posted at

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_errorjson_last_error_msg 関数で判明する。エラーがない場合は json_last_errorJSON_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_SUBSTITUTEJSON_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
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0