json_last_error
Returns an integer error code describing why the last JSON encode, decode or validate call failed.
json_last_error(): int- Added in PHP 5.3.0
Return value
intOne of the JSON_ERROR_* constants: JSON_ERROR_NONE (0) when the last call succeeded, otherwise a code such as JSON_ERROR_DEPTH, JSON_ERROR_SYNTAX, JSON_ERROR_UTF8 or JSON_ERROR_INF_OR_NAN.
Changelog
- PHP 8.1.0JSON_ERROR_NON_BACKED_ENUM was added, returned when json_encode() is given a pure (non-backed) enum case.
- PHP 7.3.0Calls that pass JSON_THROW_ON_ERROR no longer update the value returned here; they throw a JsonException instead.
json_last_error reports why the most recent json_encode(), json_decode() or json_validate() call failed. It returns JSON_ERROR_NONE (the integer 0) when that call succeeded, so the standard check is json_last_error() === JSON_ERROR_NONE.
It exists because both encode and decode signal failure with values you cannot trust on their own: json_decode() returns null, which is also a legitimate decoded value, and json_encode() returns false. The error code is the only way to tell a real failure from a valid falsy result if you are not using exceptions.
The state is global and short-lived
There is one error slot for the whole request. Every subsequent JSON call overwrites it — including a successful one, which resets it to JSON_ERROR_NONE. Read it on the line immediately after the call you care about, never later, and never after any helper that might itself touch JSON.
Calls made with JSON_THROW_ON_ERROR deliberately leave the slot untouched, so this function is useless alongside that flag. On PHP 7.3 and up, prefer JSON_THROW_ON_ERROR plus a catch (JsonException $e) block for new code; reach for json_last_error when you need to keep working with older code paths or want to branch on a specific error code without exceptions.
Example
When to use it
- Decide whether a null from json_decode means invalid input or the literal JSON value null.
- Branch on JSON_ERROR_DEPTH to reject oversized payloads with a different HTTP status than a syntax error.
- Log a machine-readable error code for a failed webhook body alongside the raw payload.
- Keep JSON error handling working on codebases that still target PHP 7.2 or older.
Frequently asked questions
What is the difference between json_last_error and json_last_error_msg?
json_last_error returns an integer constant you can compare and switch on, while json_last_error_msg returns the same failure as a human-readable English sentence. Branch on the constant and log the message.
Why does json_last_error return 0 when my JSON clearly failed?
Either another JSON call ran in between and reset the state, or the failing call used JSON_THROW_ON_ERROR, which throws a JsonException and never updates the error slot. Read it on the line immediately after the call you are checking.
Do I still need json_last_error in modern PHP?
Not for new code on PHP 7.3 and up, where JSON_THROW_ON_ERROR with a try/catch is clearer and cannot go stale. It remains useful for older code and when you want to branch on a specific code without exception handling.
More examples
Telling a real failure from a decoded null
Both calls make json_decode return null, so a null check alone cannot separate them. Checking the error code shows the first input is the valid JSON literal null (ok = true) while the second is a typo that produced a syntax error.
<?php
function decode(string $raw): array {
$value = json_decode($raw, true);
if (json_last_error() !== JSON_ERROR_NONE) {
return ['ok' => false, 'why' => json_last_error_msg()];
}
return ['ok' => true, 'value' => $value];
}
print_r(decode('null'));
print_r(decode('nul'));Branching on the specific error code
The constants let you react differently to each failure: a syntax error is the client's fault (400), an excessive depth may deserve a payload-too-large response, and a UTF-8 error usually means the sender used the wrong charset. Matching on the integer code is stable across PHP versions in a way the message text is not.
<?php
$cases = ['{"a":1}', '{"a":}', '[[[[1]]]]', "{\"s\":\"\xB1\"}"];
foreach ($cases as $raw) {
json_decode($raw, true, 3);
echo match (json_last_error()) {
JSON_ERROR_NONE => 'ok',
JSON_ERROR_DEPTH => 'too deeply nested',
JSON_ERROR_SYNTAX => 'malformed',
JSON_ERROR_UTF8 => 'bad encoding',
default => 'other: ' . json_last_error_msg(),
}, "\n";
}Why JSON_THROW_ON_ERROR makes this function lie
The throwing call fails but deliberately leaves the global error slot alone, so the final json_last_error() still reports the earlier failure. Mixing the two error styles in one codebase produces stale, misleading codes; pick exceptions or pick json_last_error, not both.
<?php
json_decode('{oops}');
echo json_last_error_msg(), "\n";
try {
json_decode('{oops}', true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
echo 'thrown: ', $e->getMessage(), "\n";
}
// Still the code from the FIRST call - the throwing call never updated it.
echo json_last_error(), "\n";
Discussion