json_last_error

Returns an integer error code describing why the last JSON encode, decode or validate call failed.

Signature
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

Try it yourself
Loading editor…
Press Run to execute the code.

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.

Example · php
<?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.

Example · php
<?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.

Example · php
<?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

  • Be the first to comment on this lesson.