Take the State of PHP 2026 Survey →

Opt-in JSON Comments and Trailing Commas View as Markdown

Under discussion
▴ 2 ▾ 0
community vote
Authors
Otar Chekurishvili, otar@hey.com
Date
2026-10-13
Version
1.0
Implementation
https://github.com/php/php-src/pull/23336
Source
wiki.php.net/rfc/json_comments_trailing_commas

Votes

Implement JSON_ALLOW_COMMENTS as outlined in the RFC? no votes
Yes · No · Abstain
Implement JSON_ALLOW_TRAILING_COMMAS as outlined in the RFC? no votes
Yes · No · Abstain

Discussions on the mailing list

[Pre-RFC] Opt-in JSON comments and trailing commas
by Otar Chekurishvili · 4 messages
RFC karma request: Opt-in JSON Comments and Trailing Commas
by Otar Chekurishvili · 3 messages
[RFC] Opt-in JSON Comments and Trailing Commas
by Otar Chekurishvili · 3 messages

RFC text

Copied from wiki.php.net, last synchronized Oct 13, 2026 19:46 UTC

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_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().

JSON_ALLOW_COMMENTS

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

  • A line comment starts with //. It ends at the end of the line or at the end of the input.
  • A block comment starts with /* 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:

  • A comment can occur at each position where JSON permits whitespace: before a value, after a value, and between two tokens.
  • A comment cannot occur inside a token. For example, tr/**/ue and -/**/1 cause a syntax error. A comment can directly follow a token, for example 1.5/*note*/.
  • Text inside a string is never a comment. "https://example.com/*" is a normal string.
  • A line comment ends at a line feed (LF), at a carriage return (CR), or at CR LF. U+2028 and U+2029 do not end a line comment.
  • Block comments do not nest. The first */ ends the comment.
  • The * in /* cannot also close the comment. Thus /*/1 is an unterminated comment, and /**/1 is the value 1.
  • The parser does not accept # comments.
  • A comment is not a value. Input with only comments causes a syntax error, the same as empty input.
  • A comment can contain all valid UTF-8 characters except NUL (U+0000). This includes the control characters U+0001 to U+001F. These characters cannot go into the result, because the parser discards the comment text.
  • A NUL byte in a comment causes 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.
  • Invalid UTF-8 in a comment causes 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.
  • An unterminated block comment causes JSON_ERROR_SYNTAX. The error location is the position of its /*.
  • Error locations count the lines inside comments. As outside comments, only LF and CR LF start a new line. A CR alone ends a line comment, but it does not start a new line. Thus near location 3:5 always points to the correct line of the original input.

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

  • Without the new flags, the parser gives the same result, error code and error location as before. A differential test with 120,025 inputs found no difference (see Patches and Tests).
  • The new flags do not change the result for valid strict JSON.
  • 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.
  • Comments and trailing commas do not change the nesting depth. The $depth argument operates as before.
  • For duplicate keys, the last value is the result, as before.
  • There are no new error codes. Without its flag, a comment or a trailing comma causes JSON_ERROR_SYNTAX at the same location as before.
  • A UTF-8 byte order mark (BOM) at the start of the input still causes a syntax error.

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:

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

Why not in userland

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

  • The library must know JSON strings. A simple regular expression can damage strings such as "https://example.com/*".
  • After the library removes comments, the error locations do not match the original file.
  • The library copies the input and reads it two times.
  • 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.

Backward Incompatible Changes

  • The RFC adds two global constants. If code defines a global constant with the same name, PHP shows the warning “Constant JSON_ALLOW_COMMENTS already defined, this will be an error in PHP 9” and keeps the built-in value. This RFC does not affect constants in a namespace. A Sourcegraph code search on 2026-10-12 found no PHP code that uses these names.
  • Today, 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.
  • The ValueError message of json_validate() for an invalid flag changes. It now names all three accepted flags.

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:

  • The scanner keeps the line data of an open block comment in the 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.
  • After whitespace or a line break, the scanner jumps directly back to the JSON state. It does not go through the state dispatch again.

Code review can add these changes before the merge.

Open Issues

None.

Unaffected PHP Functionality

  • json_encode() and JsonSerializable.
  • json_decode() and json_validate() without the new flags.
  • All current error codes and error messages.

Future Scope

  • Later RFCs can add more opt-in flags for other syntax features, if users need them. Examples are single-quoted strings, unquoted keys and hexadecimal numbers. Each new flag needs its own RFC. This RFC does not propose them.
  • If a format such as JSONC gets a stable specification, a later RFC can add one constant that combines the flags for this format.

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.


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)

Patches and Tests

  • Implementation: https://github.com/php/php-src/pull/23336
  • The PR adds six test files to 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.
  • The JSON fuzzer (sapi/fuzzer/fuzzer-json.c) also runs with the new flags. Its dictionary (sapi/fuzzer/json.dict) contains the new tokens.
  • The PR adds UPGRADING notes for the two flags and the two constants.
  • Differential test: generated 120,025 inputs. The test decoded each input with 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.
  • Superset test: 21,963 checks with valid strict JSON gave the same result with all three combinations of the new flags.

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:

  • There is no stable JSONC standard. The jsonc.org specification is a draft that can change. A flag that names a format must follow that format when the format changes.
  • The JSONC draft makes trailing commas optional: “JSONC parsers MAY support trailing commas”. Its reference parser, jsonc-parser, rejects trailing commas by default. Thus a JSON_ALLOW_JSONC flag cannot define trailing commas by reference to JSONC.
  • The name JSONC has more than one meaning. The JWCC article lists two different formats with the name JSONC.
  • Each flag has one exact meaning that does not change. Users can enable only the feature that they need. For example, a project can accept comments and keep strict commas.
  • Two flags give four combinations. The tests and the fuzzer cover all four combinations. Each new flag needs its own RFC, so the number of flags increases only after a vote.
  • Other JSON parsers also use separate flags (see Prior art).

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

  • 1.0 (2026-10-12): First version on the wiki. Changes after the pre-RFC thread and the first draft implementation:
    • The RFC explains the relation to JSON5, JSONC and JWCC.
    • The RFC gives the reasons for two flags, not one JSON_ALLOW_JSONC flag.
    • A NUL byte in a comment now causes JSON_ERROR_CTRL_CHAR. The first draft accepted it as comment text.
    • A CR alone in a comment no longer starts a new line in error locations.
    • The RFC adds test results and performance measurements.