boundwize / jsonrecast
Editable JSON AST with visitor traversal and formatting-preserving printing.
Fund package maintenance!
0.3.23
2026-07-10 06:27 UTC
Requires
- php: ^8.2
Requires (Dev)
- boundwize/pyrameter: ^0.4.2
- boundwize/structarmed: ^0.14.9
- laminas/laminas-coding-standard: ^3.1
- phpstan/phpstan: ^2.2
- phpunit/phpunit: ^11.5
- rector/rector: ^2.4
This package is auto-updated.
Last update: 2026-07-10 06:27:50 UTC
README
Editable JSON AST with visitor traversal and formatting-preserving printing.
JsonRecast parses JSON into an editable AST, lets you transform it with path-aware visitors, and prints the result while keeping the original formatting where possible. Its traversal model is inspired by nikic/PHP-Parser, applied to JSON documents for tools that update files without creating noisy diffs.
Installation
composer require boundwize/jsonrecast
Example
This example updates a composer.json-style document in one traversal:
- Parse the JSON into a
JsonDocument. - Traverse the document with a visitor and return changed nodes.
- Replace the root
namevalue. - Remove the root
minimum-stabilityentry. - Add a PSR-4 namespace under
autoload.psr-4. - Remove a stale value from
autoload-dev.classmap. - In
leaveNode(), remove the now-emptyautoload-devparent. - Print the result while preserving the original formatting style.
use Boundwize\JsonRecast\JsonRecast; use Boundwize\JsonRecast\Node\ArrayNode; use Boundwize\JsonRecast\Node\NodeJson; use Boundwize\JsonRecast\Node\ObjectItemNode; use Boundwize\JsonRecast\Node\ObjectNode; use Boundwize\JsonRecast\Node\StringNode; use Boundwize\JsonRecast\NodePath\NodeJsonPath; use Boundwize\JsonRecast\NodeVisitor\NodeJsonVisitor; use Boundwize\JsonRecast\NodeVisitor\NodeJsonVisitorAbstract; $json = <<<'JSON' { "name": "acme/demo", "autoload": { "psr-4": { "App\\": "app/" } }, "autoload-dev": { "classmap": [ "tests/Fixtures/App" ] }, "minimum-stability": "dev" } JSON; // 1. Parse the source JSON into an editable document node. $document = JsonRecast::parse($json); // 2. Traverse the document and return changed nodes so JsonRecast can track edits. $result = JsonRecast::traverse($document, new class extends NodeJsonVisitorAbstract { public function enterNode(NodeJson $node, NodeJsonPath $path): null|NodeJson|int { if ($node instanceof ObjectItemNode && $path->isRoot()) { // 3. Replace the root "name" value. if ($node->key->value === 'name') { $node->value = new StringNode('boundwize/jsonrecast'); return $node; } // 4. Remove an unwanted root object item. if ($node->key->value === 'minimum-stability') { return NodeJsonVisitor::REMOVE_NODE; } } // 5. Add a new namespace under "autoload.psr-4". if ($node instanceof ObjectNode && $path->matches(['autoload', 'psr-4'])) { $node->set('Boundwize\\JsonRecast\\', new StringNode('src/')); return $node; } // 6. Remove a stale classmap entry. if ($node instanceof ArrayNode && $path->matches(['autoload-dev', 'classmap'])) { foreach ($node->items as $index => $item) { if ($item->value instanceof StringNode && $item->value->value === 'tests/Fixtures/App') { $node->removeAt($index); return $node; } } } return null; } // 7. After child edits are done, remove the empty "autoload-dev" parent. public function leaveNode(NodeJson $node, NodeJsonPath $path): ?int { if ( ! $node instanceof ObjectItemNode || ! $path->isRoot() || $node->key->value !== 'autoload-dev' || ! $node->value instanceof ObjectNode ) { return null; } $classmapItem = $node->value->get('classmap'); if ( $classmapItem instanceof ObjectItemNode && $classmapItem->value instanceof ArrayNode && $classmapItem->value->items === [] ) { return NodeJsonVisitor::REMOVE_NODE; } return null; } }); // 8. Print with the original formatting preserved where possible. echo JsonRecast::print($result);
That will output:
{
"name": "boundwize/jsonrecast",
"autoload": {
"psr-4": {
"App\\": "app/",
"Boundwize\\JsonRecast\\": "src/"
}
}
}
Documentation
Read the full documentation at https://boundwize.github.io/jsonrecast/.