ergebnis / json-parser
Provides a JSON parser producing an abstract syntax tree that keeps raw strings, numbers, and duplicate property names, with a printer and a traverser.
Requires
- php: ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0 || ~8.6.0
- ext-json: *
- ergebnis/json-pointer: ^3.8.0
Requires (Dev)
- ergebnis/data-provider: ^3.6.0
- ergebnis/license: ^2.7.0
- ergebnis/php-cs-fixer-config: ^6.63.3
- ergebnis/phpstan-rules: ^2.13.1
- ergebnis/phpunit-slow-test-detector: ^2.25.0
- ergebnis/rector-rules: ^1.18.3
- fakerphp/faker: ^1.24.1
- infection/infection: ^0.26.6
- phpbench/phpbench: ^1.2.14
- phpstan/extension-installer: ^1.4.3
- phpstan/phpstan: ^2.2.14
- phpstan/phpstan-deprecation-rules: ^2.0.5
- phpstan/phpstan-phpunit: ^2.0.18
- phpstan/phpstan-strict-rules: ^2.0.12
- phpunit/phpunit: ^9.6.36
- rector/rector: ^2.6.7
- symfony/finder: ^5.4.45
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-09-26 09:50:07 UTC
README
This project provides a composer package with a JSON parser producing an abstract syntax tree that keeps the raw text of strings and numbers as well as duplicate property names, with a printer that prints the tree back to JSON and a traverser that changes the tree.
Installation
Run
composer require ergebnis/json-parser
Usage
Parsing
<?php declare(strict_types=1); use Ergebnis\Json\Parser; $parser = new Parser\Parser(); $node = $parser->parse( Parser\Raw::fromString('{"name": "ergebnis/json-parser", "version": 1.0, "tag": "a", "tag": "b"}'), Parser\MaximumDepth::default(), );
Parser::parse() returns the root of an abstract syntax tree of ArrayNode, BooleanNode, NullNode, NumberNode, ObjectNode, and StringNode nodes. The tree keeps the raw text of strings and numbers, the order of properties, and duplicate property names. The parser accepts exactly RFC 8259 and throws an InvalidJson with the position of the first error otherwise. MaximumDepth::default() limits the nesting depth to 512, like json_decode().
Traversing
A Traverser walks the tree and calls enter() and leave() on each of its visitors for every node. A visitor implements Traverser\Visitor and answers with an EnterAction (keep(), replace(), remove(), or skipChildren()) or a LeaveAction (keep(), replace(), or remove()), and the traverser changes the tree in place.
<?php declare(strict_types=1); use Ergebnis\Json\Parser; final class CommentRemover implements Parser\Traverser\Visitor { public function enter( Parser\Node\Node $node, Parser\Traverser\Path $path ): Parser\Traverser\EnterAction { if ('/extra/patches' === $path->toJsonPointer()->toJsonString()) { return Parser\Traverser\EnterAction::skipChildren(); } $name = $path->name(); if ( $name instanceof Parser\Node\StringNode && '_comment' === $name->toString() ) { return Parser\Traverser\EnterAction::remove(); } return Parser\Traverser\EnterAction::keep(); } public function leave( Parser\Node\Node $node, Parser\Traverser\Path $path ): Parser\Traverser\LeaveAction { return Parser\Traverser\LeaveAction::keep(); } } $parser = new Parser\Parser(); $printer = new Parser\Printer(); $traverser = new Parser\Traverser\Traverser(new CommentRemover()); $node = $parser->parse( Parser\Raw::fromString('{"_comment": "Removed", "name": "ergebnis/json-parser", "extra": {"_comment": "Removed", "patches": {"_comment": "Kept"}}}'), Parser\MaximumDepth::default(), ); echo $printer->print( $traverser->traverse($node), Parser\Format::compact(), ); // {"name":"ergebnis/json-parser","extra":{"patches":{"_comment":"Kept"}}}
Printing
<?php declare(strict_types=1); use Ergebnis\Json\Parser; $parser = new Parser\Parser(); $printer = new Parser\Printer(); $node = $parser->parse( Parser\Raw::fromString('{"name":"ergebnis/json-parser","homepage":"https:\/\/github.com\/ergebnis\/json-parser","version":1.0,"size":1E+3,"keywords":["json","parser"]}'), Parser\MaximumDepth::default(), ); echo $printer->print( $node, Parser\Format::create( Parser\Indent::create( Parser\IndentSize::fromInt(4), Parser\IndentStyle::space(), ), Parser\NewLine::lf(), Parser\FinalNewLine::present(), ), );
This prints
{
"name": "ergebnis/json-parser",
"homepage": "https:\/\/github.com\/ergebnis\/json-parser",
"version": 1.0,
"size": 1E+3,
"keywords": [
"json",
"parser"
]
}
Printer::print() prints strings and numbers exactly as they were parsed, so escapes like \/ and spellings like 1.0 and 1E+3 survive, where a round trip through json_decode() and json_encode() would change them. Format::compact() prints without insignificant whitespace, and Format::fromRaw() detects the indent, new line, and final new line of a JSON text, so that a changed document can be printed with the layout it had.
Changelog
The maintainers of this project record notable changes to this project in a changelog.
Contributing
The maintainers of this project suggest following the contribution guide.
Code of Conduct
The maintainers of this project ask contributors to follow the code of conduct.
General Support Policy
The maintainers of this project provide limited support.
PHP Version Support Policy
This project currently supports the following PHP versions:
- PHP 7.4 (has reached its end of life on November 28, 2022)
- PHP 8.0 (has reached its end of life on November 26, 2023)
- PHP 8.1 (has reached its end of life on December 31, 2025)
- PHP 8.2
- PHP 8.3
- PHP 8.4
- PHP 8.5
The maintainers of this project add support for a PHP version following its initial release and may drop support for a PHP version when it has reached its end of life.
Security Policy
This project has a security policy.
License
This project uses the MIT license.
Credits
The design of the parser, the abstract syntax tree, and the printer is inspired by nikic/php-parser, originally licensed under BSD-3-Clause by Nikita Popov.
The test fixtures in test/Fixture/JSONTestSuite/ are copied from nst/JSONTestSuite, originally licensed under MIT by Nicolas Seriot.
Social
Follow @localheinz and @ergebnis on Twitter.