json_validateでJSONの妥当性だけ調べる
json_validateは、文字列がJSONとして正しいかどうかだけを真偽値で返す、PHP 8.3で追加された関数です。
JSONの中身が要らない場面で、これまでは仕方なくjson_decodeを使っていました。デコード結果を捨てるためだけに配列やオブジェクトを組み立てさせていたわけで、地味に無駄だなと思いながら書いていた記憶があります。json_validateはそのもやもやに対する、わりとピンポイントな解決策です。
json_decodeでの妥当性チェックは何が問題だったのか
PHP 8.2以前でJSONの妥当性だけを確認したいとき、定番だったのはこの書き方です。
$decoded = json_decode($json);
if (json_last_error() !== JSON_ERROR_NONE) {
// 不正なJSON
}
動くには動くのですが、json_decodeは文字列を解析しながら配列やオブジェクトのツリーを実際に組み立てます。値が要らないのに、その構築コストを毎回払っていることになります。受け取ったJSONが数MB単位になるようなAPI連携だと、これが地味に効いてきます。
json_validateはどう使うのか
シグネチャはjson_decodeと似ていますが、返るのは真偽値だけです。
json_validate(string $json, int $depth = 512, int $flags = 0): bool
実際に使うとこうなります。
var_dump(json_validate('{"name": "nori", "age": 40}')); // bool(true)
var_dump(json_validate('{"name": "nori",}')); // bool(false) 末尾カンマは不正
var_dump(json_validate('not json')); // bool(false)
json_decodeと同じJSONパーサーを使って検証だけ行い、デコード後の値を作らないので、メモリと処理時間の両方で軽く済みます。値を使う予定がないなら、まずこちらを検討していい関数だと思います。
depthとflagsはどう効くのか
第2引数のdepthは、json_decodeのdepth引数と同じ意味で、ネストの最大深度です。デフォルトは512。これを超えるネストのJSONは、内容が正しくてもfalseになります。
$deep = str_repeat('[', 10) . '1' . str_repeat(']', 10);
var_dump(json_validate($deep, 5)); // bool(false) 深さ制限の5を超えている
var_dump(json_validate($deep, 20)); // bool(true)
第3引数のflagsは、今のところJSON_INVALID_UTF8_IGNOREだけが指定できます。不正なUTF-8バイト列を許容して検証を通したい場合に使いますが、外部から受け取るJSONで多用するものではなく、限定的な用途だと捉えています。
実務ではどこで使うのか
私が一番使いたくなるのは、Webhookの受信やキューに積まれたメッセージのペイロードチェックです。中身をすぐには使わず、まず「これは処理できる形か」だけを先に弾きたい場面ですね。
function handleWebhookPayload(string $rawBody): void
{
if (!json_validate($rawBody)) {
throw new \InvalidArgumentException('invalid json payload');
}
// ここでようやくjson_decodeして中身を使う
$payload = json_decode($rawBody, true, flags: JSON_THROW_ON_ERROR);
// ...
}
妥当性チェックと実際のデコードを分けておくと、「壊れたJSONを受け取った」という事実だけをログに残したい場合にも、デコード例外のノイズに埋もれずに済みます。個人的には、この分離がわりと効くケースだと思っています。
まとめ
json_validateは、JSONの値そのものが要らず妥当性だけ確かめたいときに使う、PHP 8.3からの追加関数です。json_decodeを流用して結果を捨てるのに比べて、意図もはっきりしますし、余計なメモリも使いません。PHP 8.3以降が動作環境なら、バリデーション目的でjson_decodeを呼んでいる箇所は置き換えを検討してよさそうです。
よくある質問
Q. json_validateはPHP 8.2以前でも使えますか?
A. 使えません。PHP 8.3で追加された関数なので、8.2以前の環境ではsymfony/polyfill-php83などのポリフィルを使うか、従来通りjson_decodeとjson_last_errorで代用する必要があります。
Q. json_validateとjson_decodeの結果は必ず一致しますか?
A. 基本的には一致します。同じJSONパーサーを使っているため、json_validateがtrueを返す文字列はjson_decodeでも正常にデコードできます。ただしdepthやflagsを両者で揃えていないと、片方だけ通る・通らないという食い違いが起きるので注意してください。
Q. json_validateは大きなJSONでも速いですか?
A. json_decodeのように値のツリーを構築しない分、同じ内容のJSONを検証だけするなら一般的に軽く済みます。ただし解析自体はJSON全体を走査するので、サイズに比例した処理時間はかかります。