JSON7 min readExamples run on PHP 8.5.10

json_last_error()

Learn how json_last_error() reports the error code from the last JSON call, how it resets, and why it must be read straight after json_decode() or json_encode().

Signature

json_last_error(): int
Available since PHP 5.3.07.48.08.18.28.38.48.5All versions →

Purpose

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 contains NAN or INF.
  • 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.

Example
<?php
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";
}
Output
{"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.

Example
<?php
json_encode("\xB1\x31");
echo json_encode([
    'isUtf8Error' => json_last_error() === JSON_ERROR_UTF8,
]), "\n";
Output
{"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.

Example
<?php
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";
Output
{"afterFailure":true,"afterSuccess":true,"unchanged":true}

Edge cases

  • Valid null: json_decode('null') returns null with no error. Use json_last_error() === JSON_ERROR_NONE to distinguish it from a failure.
  • Depth limits: if you pass a small depth to json_decode(), such as 1 for the input [[1]], the nesting can exceed it and the state becomes JSON_ERROR_DEPTH.
  • Throwing mode: with JSON_THROW_ON_ERROR, failures arrive as a JsonException, 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, INF or recursion cannot be encoded normally and set the matching error code.

Common mistakes

  1. Reading it too late. Another JSON call between the failure and your check can overwrite the state. Check immediately.
  2. Testing only for null. A null result can be legitimate input, so look at the error code as well.
  3. Expecting it to track throwing calls. Code that always passes JSON_THROW_ON_ERROR will not see anything meaningful in json_last_error().
  4. Comparing with literal numbers. Constants make the intent clear and survive any change in the numeric values.
  5. Assuming a stale error is current. Run the JSON call first, then check. A leftover value from an earlier call can mislead you.
  • 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

Checking decode results with error names
<?php
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";
}
Output
{"name":"Ada"} => none
{'name':'Ada'} => syntax
nope => syntax
Detecting a UTF-8 encoding error
<?php
json_encode("\xB1\x31");
echo json_encode([
    'isUtf8Error' => json_last_error() === JSON_ERROR_UTF8,
]), "\n";
Output
{"isUtf8Error":true}
Reset behaviour and JSON_THROW_ON_ERROR
<?php
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";
Output
{"afterFailure":true,"afterSuccess":true,"unchanged":true}