Table of Contents

PHP RFC: Opt-in JSON Comments and Trailing Commas

Introduction

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.

Proposal

New constants

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.

Changed functions

JSON_ALLOW_COMMENTS

With this flag, the parser accepts two types of comments:

The parser discards each comment, so a comment has no effect on the result. These rules apply:

JSON_ALLOW_TRAILING_COMMAS

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.

Behavior that does not change

Examples

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
}

Relation to JSON5, JSONC and JWCC

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.

Prior art

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.

Demand

Many PHP projects already parse JSON with comments. Packagist data from 2026-10-12:

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.

Why not in userland

A userland library can remove comments before it calls json_decode(). This approach has problems:

The native scanner processes comments in one pass. It makes no copy, and it keeps the original error locations.

Backward Incompatible Changes

Proposed PHP Version(s)

Next minor version: PHP 8.7.

RFC Impact

To the Ecosystem

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.

To Existing Extensions

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.

To SAPIs

None.

Performance

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:

Code review can add these changes before the merge.

Open Issues

None.

Unaffected PHP Functionality

Future Scope

Voting Choices

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.


Implement JSON_ALLOW_COMMENTS as outlined in the RFC?
Real name Yes No Abstain
Final result: 0 0 0
This poll has been closed.
Implement JSON_ALLOW_TRAILING_COMMAS as outlined in the RFC?
Real name Yes No Abstain
Final result: 0 0 0
This poll has been closed.

Patches and Tests

Implementation

After the vote, this section will give the PHP version, the commit links and the link to the PHP manual.

References

Rejected Features

One JSON_ALLOW_JSONC flag

In 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:

If a stable standard appears later, a new constant can combine the flags. This needs no change to the flags of this RFC.

Full JSON5 support

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.

Hash comments

JavaScript, JSONC, JWCC and JSON5 do not have # comments. This RFC does not accept them.

Nested block comments

JavaScript and the JSONC draft do not nest block comments. This RFC uses the same rule: the first */ ends the comment.

More than one trailing comma

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].

Comment removal before parsing

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.

Changelog