Search by

ergebnis / json-parser

localheinz

Provides a JSON parser producing an abstract syntax tree that keeps raw strings, numbers, and duplicate property names, with a printer and a traverser.

dev-main / 1.0.x-dev 2026-09-26 09:49 UTC

This package is auto-updated.

Last update: 2026-09-26 09:50:07 UTC


README

Integrate Merge Release Renew

Code Coverage

Latest Stable Version Total Downloads Monthly Downloads

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:

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.