Copied from wiki.php.net, last synchronized Oct 13, 2026 19:46 UTC
Write comments in your JSON. Keep the last comma. Strict JSON stays the default. This RFC adds two opt-in flags for json_decode() and json_validate(): JSON_ALLOW_COMMENTS and JSON_ALLOW_TRAILING_COMMAS.
Many tools use JSON for configuration files that people edit. In these files, people want to explain a setting with a comment. They also want to add an item to a list without a change to the line before it. Today, json_decode() rejects comments and trailing commas. Thus PHP projects use userland libraries or regular expressions to remove comments before they call json_decode().
<?php
$config = <<<'JSON'
{
// Connection for local development
"host": "localhost",
"port": 5432, /* default PostgreSQL port */
"options": [
"persistent",
"utf8",
],
}
JSON;
$data = json_decode($config, true, flags: JSON_ALLOW_COMMENTS | JSON_ALLOW_TRAILING_COMMAS);
// ['host' => 'localhost', 'port' => 5432, 'options' => ['persistent', 'utf8']]
$data = json_decode($config, true);
// NULL, json_last_error_msg(): "Syntax error near location 2:5"
Each flag enables one syntax feature. The two flags are independent: code can use one flag or both flags. Without the flags, json_decode() and json_validate() do not change.
This RFC adds two global constants to ext/json:
<?php
/**
* Accept line comments and block comments.
* @var int
*/
const JSON_ALLOW_COMMENTS = 8388608; // 1 << 23
/**
* Accept one comma after the last item of a non-empty array or object.
* @var int
*/
const JSON_ALLOW_TRAILING_COMMAS = 16777216; // 1 << 24
The constants use bits 23 and 24. No other JSON_* flag uses these bits.
json_decode() accepts the two flags in $flags.json_validate() accepts the two flags in $flags. Before this RFC, it accepted only JSON_INVALID_UTF8_IGNORE. Other flags still cause a ValueError. The message of this ValueError now names all three accepted flags.json_encode() does not change. It never writes comments or trailing commas. The two flags have no effect on json_encode().With this flag, the parser accepts two types of comments:
//. It ends at the end of the line or at the end of the input./* and ends with */. It can contain line breaks.The parser discards each comment, so a comment has no effect on the result. These rules apply:
tr/**/ue and -/**/1 cause a syntax error. A comment can directly follow a token, for example 1.5/*note*/."https://example.com/*" is a normal string.*/ ends the comment.* in /* cannot also close the comment. Thus /*/1 is an unterminated comment, and /**/1 is the value 1.# comments.JSON_ERROR_CTRL_CHAR at the position of the NUL byte, the same as a NUL byte outside a comment. JSON_INVALID_UTF8_IGNORE and JSON_INVALID_UTF8_SUBSTITUTE do not change this. Some other parsers end a comment at a NUL byte. The error prevents a different result in PHP for the same input.JSON_ERROR_UTF8, the same as in a string. With JSON_INVALID_UTF8_IGNORE or JSON_INVALID_UTF8_SUBSTITUTE, the parser ignores invalid bytes in comments. No U+FFFD goes into the result, because the parser discards the comment text.JSON_ERROR_SYNTAX. The error location is the position of its /*.near location 3:5 always points to the correct line of the original input.With this flag, the parser accepts one comma after the last item of a non-empty array or object. In this RFC, an “item” is an array element or an object member.
| Input | Result with JSON_ALLOW_TRAILING_COMMAS |
|---|---|
[1, 2, 3,] |
[1, 2, 3] |
{“a”: 1, “b”: 2,} |
{“a”: 1, “b”: 2} |
[,] and {,} |
Syntax error: there is no item before the comma |
[1,,] and [1,,2] |
Syntax error: more than one comma after an item |
1, |
Syntax error: a top-level value cannot have a trailing comma |
[1,} |
State mismatch error, the same as for [1} |
Whitespace can occur between the trailing comma and the closing bracket. With JSON_ALLOW_COMMENTS, comments can also occur there.
JSON_OBJECT_AS_ARRAY, JSON_BIGINT_AS_STRING, JSON_INVALID_UTF8_IGNORE, JSON_INVALID_UTF8_SUBSTITUTE and JSON_THROW_ON_ERROR operate as before, also together with the new flags.$depth argument operates as before.JSON_ERROR_SYNTAX at the same location as before.With only JSON_ALLOW_COMMENTS, the trailing comma in $config from the introduction is still an error:
var_dump(json_decode($config, true, flags: JSON_ALLOW_COMMENTS));
// NULL
echo json_last_error_msg();
// Syntax error near location 8:5
A comment marker inside a string is not a comment. A regular expression that removes comments can damage this string:
var_dump(json_decode('{"url": "https://example.com/*"}', true, flags: JSON_ALLOW_COMMENTS));
// array(1) { ["url"]=> string(21) "https://example.com/*" }
For an unterminated block comment, the error location is the position of its /*:
var_dump(json_decode("[1,\n /* note", flags: JSON_ALLOW_COMMENTS));
// NULL
echo json_last_error_msg();
// Syntax error near location 2:3
json_validate() accepts the same flags:
var_dump(json_validate('{"a": 1, /* note */}', flags: JSON_ALLOW_COMMENTS | JSON_ALLOW_TRAILING_COMMAS));
// bool(true)
JSON_THROW_ON_ERROR operates as before:
try {
json_decode('[1,,]', flags: JSON_ALLOW_TRAILING_COMMAS | JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
echo $e->getMessage();
// Syntax error near location 1:4
}
In the pre-RFC thread, Larry Garfield asked if this RFC adds JSON5. It does not.
JSON5 is a much larger extension of JSON. It adds single-quoted strings, unquoted keys, hexadecimal numbers, Infinity and NaN, a leading + sign, multi-line strings, and more whitespace characters. Some of these features change the data model, because strict JSON has no NaN and no Infinity.
The two flags of this RFC change only the syntax. If you remove all comments and trailing commas from the input, you get strict JSON with the same value.
As Anton Smirnov wrote in the pre-RFC thread, the two flags are close to JSONC and to JWCC (JSON With Commas and Comments):
| Syntax | Strict JSON | JSON_ALLOW_COMMENTS | Both flags | JSONC (draft) | JWCC | JSON5 |
|---|---|---|---|---|---|---|
// and /* */ comments |
No | Yes | Yes | Yes | Yes | Yes |
| One trailing comma | No | No | Yes | Optional | Yes | Yes |
| A CR alone ends a line comment | - | Yes | Yes | Yes | No | Yes |
| U+2028 and U+2029 end a line comment | - | No | No | No | No | Yes |
Single quotes, unquoted keys, hexadecimal numbers, NaN, Infinity |
No | No | No | No | No | Yes |
Thus JSON_ALLOW_COMMENTS accepts the syntax of the JSONC draft without trailing commas. Both flags together accept JWCC. Tailscale uses JWCC in Go with the name HuJSON. There is one small difference: in JWCC, only LF ends a line comment. In this RFC, a CR alone also ends a line comment, as in the JSONC draft.
The flags do not bind PHP to JSON5 or to a JSONC specification. Each flag names one syntax feature with one exact meaning. Later RFCs can add more flags, one feature at a time. For the reasons against one JSON_ALLOW_JSONC flag, see Rejected Features.
Other JSON parsers also use one flag for each syntax feature:
| Parser | Comments | Trailing commas |
|---|---|---|
Chromium base::JSONReader (C++) |
JSON_ALLOW_COMMENTS |
JSON_ALLOW_TRAILING_COMMAS |
| RapidJSON (C++) | kParseCommentsFlag |
kParseTrailingCommasFlag |
| Jackson (Java) | JsonReadFeature.ALLOW_JAVA_COMMENTS |
JsonReadFeature.ALLOW_TRAILING_COMMA |
.NET System.Text.Json |
JsonCommentHandling.Skip |
AllowTrailingCommas |
| seld/jsonlint (PHP, used by Composer) | JsonParser::ALLOW_COMMENTS |
- |
Chromium uses the same two names as this RFC.
Many PHP projects already parse JSON with comments. Packagist data from 2026-10-12:
colinodell/json5 (a JSON5 parser): 30.2 million installs, 1.76 million in the last month. For example, infection/infection uses it to read the infection.json5 configuration file.adhocore/json-comment (a comment remover): 737,000 installs.seld/jsonlint added the JsonParser::ALLOW_COMMENTS flag in version 1.10.0 (2023).Many common configuration files contain comments, for example VS Code settings.json, TypeScript tsconfig.json and Dev Container devcontainer.json. PHP tools that read these files need a parser that accepts comments.
A userland library can remove comments before it calls json_decode(). This approach has problems:
"https://example.com/*".json_validate() cannot check such files.The native scanner processes comments in one pass. It makes no copy, and it keeps the original error locations.
json_decode() ignores unknown flag bits. If code gives bit 23 or bit 24 by mistake, input with comments or trailing commas can now decode without an error. The result for valid strict JSON does not change.ValueError message of json_validate() for an invalid flag changes. It now names all three accepted flags.Next minor version: PHP 8.7.
IDEs, language servers and static analyzers must add the two constants to their stubs, for example JetBrains phpstorm-stubs, PHPStan and Psalm. A library that also supports older PHP versions can check PHP_VERSION_ID >= 80700. Then it can use the native flags and keep its userland parser as a fallback.
Only ext/json changes. The internal C parser API (php_json_parser_init()) accepts the two new option bits. Other extensions that use this API see no change, unless they give the new bits.
The PR adds the macros PHP_JSON_ALLOW_COMMENTS and PHP_JSON_ALLOW_TRAILING_COMMAS to the public header php_json.h. The labeler bot of php-src marks each change to this header as “ABI break”. The PR does not change a struct or a function signature.
None.
I have measured CPU instructions with Callgrind (Valgrind 3.22, GCC 13.3, -O2) on commit a4cd163 of the PR and its master base. The input was a pretty-printed JSON document of 7.6 MB with 20,000 objects. The count includes only the call of json_decode() or json_validate().
| Call and input | master | This RFC | Change |
|---|---|---|---|
json_decode(), strict JSON, no new flag |
354,777,244 | 360,436,080 | +1.60% |
json_validate(), strict JSON, no new flag |
333,889,491 | 339,548,009 | +1.69% |
json_decode(), strict JSON, both new flags |
- | 360,436,080 | Same as without the flags |
json_decode(), 10.8 MB with a line comment on each line, both new flags |
- | 405,796,303 | - |
All extra instructions are in the scanner. The Bison parser uses the same number of instructions as before. Two small scanner changes reduce the extra cost for strict JSON to +0.56% (json_decode()) and +0.59% (json_validate()). These changes do not change behavior:
php_json_scanner struct. The current code fills two local variables at the start of each php_json_scan() call. This change adds two fields to the struct.Code review can add these changes before the merge.
None.
json_encode() and JsonSerializable.json_decode() and json_validate() without the new flags.This RFC has two primary votes, one for each flag. Each vote needs a 2/3 majority. The flags are independent, so the result of one vote does not affect the other vote.
Vote: Implement JSON_ALLOW_COMMENTS as outlined in the RFC? — Yes: 0 · No: 0 · Abstain: 0 (closed)
Vote: Implement JSON_ALLOW_TRAILING_COMMAS as outlined in the RFC? — Yes: 0 · No: 0 · Abstain: 0 (closed)
ext/json/tests and updates json_validate_002.phpt. The tests cover comment positions, comment delimiters, malformed comments, NUL bytes and invalid UTF-8 in comments, trailing commas, other flags and error locations.sapi/fuzzer/fuzzer-json.c) also runs with the new flags. Its dictionary (sapi/fuzzer/json.dict) contains the new tokens.json_decode() and five current flag sets, and validated it with json_validate() and two flag sets (840,175 checks). The results, error codes and error messages are the same on master and on this branch.After the vote, this section will give the PHP version, the commit links and the link to the PHP manual.
base::JSONReader flags: https://chromium.googlesource.com/chromium/src/+/main/base/json/json_reader.hJsonReadFeature: https://fasterxml.github.io/jackson-core/javadoc/2.14/com/fasterxml/jackson/core/json/JsonReadFeature.htmlSystem.Text.Json, comments and trailing commas: https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/invalid-jsonjson_validate(): https://wiki.php.net/rfc/json_validateIn the pre-RFC thread, Tim Düsterhus proposed one flag for comments and trailing commas together. His reasons were to reference a standard, if one exists, and to prevent a combinatorial explosion of flags. This RFC keeps two flags, for these reasons:
JSON_ALLOW_JSONC flag cannot define trailing commas by reference to JSONC.If a stable standard appears later, a new constant can combine the flags. This needs no change to the flags of this RFC.
JSON5 adds many more features than comments and trailing commas. Some of them change the data model, for example NaN and Infinity. Each of these features needs its own discussion. With the flag approach, a later RFC can add one feature at a time.
JavaScript, JSONC, JWCC and JSON5 do not have # comments. This RFC does not accept them.
JavaScript and the JSONC draft do not nest block comments. This RFC uses the same rule: the first */ ends the comment.
In JavaScript, [1,,2] contains an empty array slot. JSONC, JWCC and JSON5 accept only one trailing comma. This RFC rejects [1,,] and [1,,2].
A pre-processor can remove comments and then call the strict parser. This approach changes error locations, copies the input, and must know JSON strings. The native scanner has none of these problems.
JSON_ALLOW_JSONC flag.JSON_ERROR_CTRL_CHAR. The first draft accepted it as comment text.