JSON troubleshooting

PHP json_decode returns null: Syntax error and other causes

Find out why json_decode returns null with JSON_THROW_ON_ERROR or json_last_error_msg, then fix syntax errors, BOMs, invalid UTF-8 and depth limits.

What the error means

json_decode returns null when decoding fails, and also when the JSON text is literally null. Without flags it does not say why. Pass JSON_THROW_ON_ERROR (PHP 7.3 and later) to get a JsonException, or call json_last_error_msg() right after the call. Syntax error covers most malformed input: a UTF-8 byte-order mark, single quotes, trailing commas, comments, invalid escapes, an empty string or HTML printed before the JSON. Invalid UTF-8 and control characters have their own messages. PHP does not report where the error is.

Exact messages by runtime

Messages depend on the input and runtime version. Node and Python rows were reproduced locally. Other rows cite official documentation or source. Rows marked as fragments or templates preserve the source wording and are not complete exception transcripts. Browsers and libraries may append location details.

RuntimeMessageInput and evidence
PHP / json_last_error_msg()Syntax errorjson_decode("{'id': 1}")
Official php-src 8.5.11 message; json_last_error() returns 4 (JSON_ERROR_SYNTAX)
PHP / JSON_THROW_ON_ERRORJsonException: Syntax errorjson_decode($json, flags: JSON_THROW_ON_ERROR)
Official php-src 8.5.11 source: same message, exception code 4
PHP, byte-order markSyntax error"\xEF\xBB\xBF{}", a UTF-8 BOM before the JSON
php-src 8.5.11 scanner: a UTF-8 character outside a string is a syntax error
PHP, empty stringSyntax errorjson_decode('')
php-src 8.5.11 json_decode: empty input sets JSON_ERROR_SYNTAX
PHP, invalid UTF-8Malformed UTF-8 characters, possibly incorrectly encodedWindows-1252 bytes such as \xE9 inside a string
Official php-src 8.5.11 message (JSON_ERROR_UTF8)
PHP, deep nestingMaximum stack depth exceededNesting deeper than the depth argument, 512 by default
Official php-src 8.5.11 message (JSON_ERROR_DEPTH)
PHP, valid nullNo errorjson_decode('null') returns null
Official php-src 8.5.11 message; decoding succeeded

Causes you can reproduce

The text uses JavaScript or Python syntax

Single quotes, unquoted keys, trailing commas and comments are all reported as Syntax error, without a position.

PHP
<?php
var_dump(json_decode("{'id': 1}")); // NULL
echo json_last_error_msg();         // Syntax error

A byte-order mark precedes the JSON

Files saved as "UTF-8 with BOM" start with the bytes EF BB BF. file_get_contents keeps them, and json_decode rejects them.

PHP
<?php
$json = "\xEF\xBB\xBF" . '{"id": 1}'; // as read from a file saved with a BOM
var_dump(json_decode($json)); // NULL
echo json_last_error_msg();   // Syntax error

The text is not UTF-8

JSON text must be UTF-8. An export in Windows-1252 or ISO-8859-1 fails on its first accented character.

PHP
<?php
$json = "{\"name\": \"Ren\xE9\"}"; // é encoded as Windows-1252
var_dump(json_decode($json)); // NULL
echo json_last_error_msg();   // Malformed UTF-8 characters, possibly incorrectly encoded

How to fix it

  1. Decode with JSON_THROW_ON_ERROR, or check json_last_error() immediately after json_decode. The next json_encode or json_decode call replaces the stored error.
  2. Inspect the first bytes with bin2hex(substr($json, 0, 16)). efbbbf is a byte-order mark, and 3c is <, the start of HTML or a PHP warning.
  3. Fix syntax errors where the JSON is produced. Strip a byte-order mark only where you read the file, and convert legacy encodings to UTF-8 before decoding.
  4. With exceptions enabled, a null result means the JSON text was null. Without them, compare json_last_error() with JSON_ERROR_NONE.

Decode with exceptions

The exception message is the same text json_last_error_msg() returns, and its code is the JSON_ERROR_* constant.

PHP
<?php
try {
    $data = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
    error_log('Invalid JSON: ' . $e->getMessage() . ' (code ' . $e->getCode() . ')');
    throw $e;
}

Remove a UTF-8 byte-order mark from a file

str_starts_with needs PHP 8.0 or later.

PHP
<?php
$json = file_get_contents('data.json');
if (str_starts_with($json, "\xEF\xBB\xBF")) {
    $json = substr($json, 3);
}
$data = json_decode($json, true, 512, JSON_THROW_ON_ERROR);

Convert a legacy encoding first

Name the real source encoding. mb_convert_encoding needs the mbstring extension.

PHP
<?php
$json = mb_convert_encoding($legacy, 'UTF-8', 'Windows-1252');
$data = json_decode($json, true, 512, JSON_THROW_ON_ERROR);

Find the exact position with Tool Kumar

  1. Copy the exact input text, including its original whitespace. For HTTP failures, copy the raw response body.
  2. Open the Tool Kumar JSON validator and paste the text. The document stays in your browser.
  3. Read the line and column in the error or fixable-issue banner. For an invalid document, select Go to error to move the caret to that location.
  4. Inspect the preceding token as well as the marked character. A parser marks where it discovered the problem; the missing quote, separator or value can start earlier.

Python character offsets are zero-based, while its displayed lines and columns are one-based. V8 string positions count UTF-16 code units; Go offsets count bytes read. Unicode text can make these offsets differ. Use the original text and its line/column context rather than copying an offset between runtimes.

Tool Kumar can classify some input as fixable loose JSON. Review a proposed change before using the JSON repair tool, then validate the result against your API contract. For empty input, inspect the producer or response because there is no character to select.

Frequently asked questions

Why does json_decode return null for JSON that looks valid?

Common reasons are an invisible UTF-8 byte-order mark, text that is not UTF-8, nesting deeper than the depth limit, or JSON that is literally null. Check the error message and the first bytes.

Does json_last_error_msg show where the error is?

No. PHP reports only the kind of error. Paste the text into a validator to find the line and column.

Should I use JSON_INVALID_UTF8_IGNORE or JSON_INVALID_UTF8_SUBSTITUTE?

They make decoding succeed by dropping invalid bytes or replacing them with U+FFFD, which changes the data. Prefer converting the text from its real encoding.

Why does JSON from a WordPress POST field fail?

WordPress adds slashes to request data, so quotes arrive escaped with backslashes. Remove them with wp_unslash() before calling json_decode.

Standards and diagnostic sources

RFC 8259 and ECMA-404 define JSON syntax. Diagnostic wording belongs to the parser. The table links each cited message to its source.