Signature
json_last_error(): intPurpose
json_last_error() tells you whether the most recent JSON operation failed, and if so, why. PHP's JSON functions often signal trouble in ways that are easy to miss. For example, json_decode() returns null both for the JSON text null and for broken input. The error code is how you tell those cases apart.
The function reads a global error state. It applies to the last JSON validation, encoding or decoding call that did not use JSON_THROW_ON_ERROR.
Signature
Parameters and defaults
The function takes no parameters, so there is nothing to configure or default. It only reads state left by an earlier call.
Return value and failure cases
The return value is always an integer. Compare it with the predefined constants instead of hard-coding numbers:
JSON_ERROR_NONE: no error occurred.JSON_ERROR_DEPTH: the maximum stack depth was exceeded.JSON_ERROR_STATE_MISMATCH: invalid or malformed JSON.JSON_ERROR_CTRL_CHAR: a control character error, possibly from incorrect encoding.JSON_ERROR_SYNTAX: a syntax error.JSON_ERROR_UTF8: malformed UTF-8 characters.JSON_ERROR_RECURSION: the value to encode contains recursive references.JSON_ERROR_INF_OR_NAN: the value to encode containsNANorINF.JSON_ERROR_UNSUPPORTED_TYPE: a value of a type that cannot be encoded was given.JSON_ERROR_INVALID_PROPERTY_NAME: a property name that cannot be encoded was given.JSON_ERROR_UTF16: malformed UTF-16 characters.JSON_ERROR_NON_BACKED_ENUM: the value contains a non-backed enum, which cannot be serialized. This constant was added in PHP 8.1.0.
json_last_error() itself never fails. It simply returns the current state.
Example 1: checking decode results
The helper below maps a few codes to short names. Each input is decoded, and the error code is read immediately afterwards.
function jsonErrorName(int $code): string
{
return match ($code) {
JSON_ERROR_NONE => 'none',
JSON_ERROR_DEPTH => 'depth',
JSON_ERROR_SYNTAX => 'syntax',
JSON_ERROR_UTF8 => 'utf8',
default => 'other',
};
}
$inputs = ['{"name":"Ada"}', "{'name':'Ada'}", 'nope'];
foreach ($inputs as $input) {
json_decode($input);
echo $input, ' => ', jsonErrorName(json_last_error()), "\n";
}{"name":"Ada"} => none
{'name':'Ada'} => syntax
nope => syntax
Single quotes are not valid JSON string delimiters, so the second input is a syntax error. The word nope is not a JSON literal, so it also fails.
Example 2: encoding errors
The function covers encoding as well as decoding. The byte sequence \xB1\x31 is not valid UTF-8, so encoding it sets JSON_ERROR_UTF8.
json_encode("\xB1\x31");
echo json_encode([
'isUtf8Error' => json_last_error() === JSON_ERROR_UTF8,
]), "\n";{"isUtf8Error":true}
The error state is read before the outer json_encode() runs, so the array describes the failed inner call.
Example 3: reset behaviour and JSON_THROW_ON_ERROR
A later call that finishes without error resets the state to JSON_ERROR_NONE. A call that uses JSON_THROW_ON_ERROR does not touch the global state at all.
json_decode('{bad');
$afterFailure = json_last_error() === JSON_ERROR_SYNTAX;
json_decode('[1,2,3]');
$afterSuccess = json_last_error() === JSON_ERROR_NONE;
json_decode('{bad');
json_decode('{"ok":true}', false, 512, JSON_THROW_ON_ERROR);
$unchanged = json_last_error() === JSON_ERROR_SYNTAX;
echo json_encode([
'afterFailure' => $afterFailure,
'afterSuccess' => $afterSuccess,
'unchanged' => $unchanged,
]), "\n";{"afterFailure":true,"afterSuccess":true,"unchanged":true}
Edge cases
- Valid
null:json_decode('null')returnsnullwith no error. Usejson_last_error() === JSON_ERROR_NONEto distinguish it from a failure. - Depth limits: if you pass a small depth to
json_decode(), such as1for the input[[1]], the nesting can exceed it and the state becomesJSON_ERROR_DEPTH. - Throwing mode: with
JSON_THROW_ON_ERROR, failures arrive as aJsonException, and the global state is neither set nor reset by that call. Use the exception's own code and message instead. - Encoding failures: values with invalid UTF-8,
NAN,INFor recursion cannot be encoded normally and set the matching error code.
Common mistakes
- Reading it too late. Another JSON call between the failure and your check can overwrite the state. Check immediately.
- Testing only for
null. Anullresult can be legitimate input, so look at the error code as well. - Expecting it to track throwing calls. Code that always passes
JSON_THROW_ON_ERRORwill not see anything meaningful injson_last_error(). - Comparing with literal numbers. Constants make the intent clear and survive any change in the numeric values.
- Assuming a stale error is current. Run the JSON call first, then check. A leftover value from an earlier call can mislead you.
Related functions
json_last_error_msg()returns a readable message for the same state.json_decode()parses JSON text and sets the state on failure.json_encode()produces JSON text and sets the state on failure.
Recap
json_last_error() takes no arguments and returns an integer code for the last JSON operation that did not use JSON_THROW_ON_ERROR. Compare the result with JSON_ERROR_* constants, read it immediately after the call, and do not treat a null decode result as proof of failure.
Tested examples
function jsonErrorName(int $code): string
{
return match ($code) {
JSON_ERROR_NONE => 'none',
JSON_ERROR_DEPTH => 'depth',
JSON_ERROR_SYNTAX => 'syntax',
JSON_ERROR_UTF8 => 'utf8',
default => 'other',
};
}
$inputs = ['{"name":"Ada"}', "{'name':'Ada'}", 'nope'];
foreach ($inputs as $input) {
json_decode($input);
echo $input, ' => ', jsonErrorName(json_last_error()), "\n";
}{"name":"Ada"} => none
{'name':'Ada'} => syntax
nope => syntax
json_encode("\xB1\x31");
echo json_encode([
'isUtf8Error' => json_last_error() === JSON_ERROR_UTF8,
]), "\n";{"isUtf8Error":true}
json_decode('{bad');
$afterFailure = json_last_error() === JSON_ERROR_SYNTAX;
json_decode('[1,2,3]');
$afterSuccess = json_last_error() === JSON_ERROR_NONE;
json_decode('{bad');
json_decode('{"ok":true}', false, 512, JSON_THROW_ON_ERROR);
$unchanged = json_last_error() === JSON_ERROR_SYNTAX;
echo json_encode([
'afterFailure' => $afterFailure,
'afterSuccess' => $afterSuccess,
'unchanged' => $unchanged,
]), "\n";{"afterFailure":true,"afterSuccess":true,"unchanged":true}