Parsing And Printers

JsonRecast separates parsing, format-preserving printing, and normalized pretty printing.

Contents

  1. Parse JSON
  2. Maximum Depth
  3. Preserving Printer
  4. Pretty Printer
  5. Printer Interface

Parse JSON

The facade is the shortest path:

use Boundwize\JsonRecast\JsonRecast;

$document = JsonRecast::parse($source);

You can also instantiate the parser directly:

use Boundwize\JsonRecast\Parser\JsonParser;

$document = (new JsonParser())->parse($source);

Invalid input throws ParseError.

use Boundwize\JsonRecast\Parser\ParseError;

try {
    $document = JsonRecast::parse('{"name":}');
} catch (ParseError $error) {
    printf(
        "Invalid JSON at line %d, column %d",
        $error->sourceLine,
        $error->column,
    );
}

ParseError also exposes the zero-based source $offset.

Maximum Depth

JsonRecast limits JSON nesting depth to 512 by default. The same numeric limit applies when parsing JSON, building nodes from PHP values, traversing node trees, and printing node trees.

Parsing, traversal, and printing use the json_decode()-compatible depth boundary. JsonValue::from() retains json_encode() depth semantics, which allow a container at the exact boundary that json_decode() rejects. Consequently, conversion can succeed while traversal or printing at the same maximum depth rejects the resulting tree. Printers validate before emitting output, so anything successfully printed at a given limit remains parseable at that same limit.

If parsed input exceeds the configured limit, parsing throws a catchable ParseError with the message Maximum stack depth exceeded. If PHP value conversion, traversal, or printing exceeds the configured limit, JsonRecast throws InvalidArgumentException with the same message.

You can raise or lower the limit when parsing through the facade:

use Boundwize\JsonRecast\JsonRecast;

$document = JsonRecast::parse($source, maximumDepth: 1024);

Or when using JsonParser directly:

use Boundwize\JsonRecast\Parser\JsonParser;

$document = (new JsonParser(maximumDepth: 1024))->parse($source);

The same option is available when converting PHP values:

use Boundwize\JsonRecast\Value\JsonValue;

$node = JsonValue::from($value, maximumDepth: 1024);

And when printing:

use Boundwize\JsonRecast\Printer\JsonPreservingPrinter;
use Boundwize\JsonRecast\Printer\JsonPrettyPrinter;

$json = JsonRecast::print($document, maximumDepth: 1024);

$preserved = (new JsonPreservingPrinter(maximumDepth: 1024))->print($document);
$pretty = (new JsonPrettyPrinter(maximumDepth: 1024))->print($document);

And when traversing:

use Boundwize\JsonRecast\NodeTraverser\NodeJsonTraverser;

$result = JsonRecast::traverse($document, $visitor, maximumDepth: 1024);

$traversalResult = (new NodeJsonTraverser(maximumDepth: 1024))->traverse($document);

And when finding nodes:

use Boundwize\JsonRecast\NodeJsonFinder;

$finder = new NodeJsonFinder(maximumDepth: 1024);

The maximum depth must be greater than 0.

Preserving Printer

JsonRecast::print() uses JsonPreservingPrinter. When it receives a parsed document without changes, it can return the original text exactly.

$document = JsonRecast::parse("{\r\n  \"name\" : \"jsonrecast\"\r\n}\r\n");

echo JsonRecast::print($document);

When it receives a JsonRecastResult, it also uses the result change set as an explicit signal that returned nodes should be rebuilt. Independently of that change set, the printer detects changes to JSON values and syntax trivia by comparing parsed nodes with their original text and checking their descendants. Direct edits to those fields and in-place visitor mutations that return null are therefore still printed.

$result = JsonRecast::traverse($document, $visitor);

echo JsonRecast::print($result);

After traversal, pass the JsonRecastResult itself to JsonRecast::print() so explicit change records are retained. The preserving printer also detects direct changes to document-level printer metadata: NodeAttributes::INDENT, NodeAttributes::NEWLINE, and NodeAttributes::TRAILING_NEWLINE. These attributes can therefore be changed on a parsed document and printed without requiring a traversal solely to mark the document as changed.

When INDENT changes, structural indentation adopts the new unit while intentional off-grid residual whitespace remains intact. Parsed object and array nodes record NodeAttributes::OPENING_LINE_INDENTATION, the actual leading whitespace on the line where the container opens. This matters for containers opened inline: their opening and closing alignment follows that source line rather than assuming every container begins at DEPTH × INDENT.

The preserving printer keeps the document newline style and trailing newline when they were present in the parsed source.

Pretty Printer

Use JsonPrettyPrinter when you want normalized output instead of format preservation.

use Boundwize\JsonRecast\Printer\JsonPrettyPrinter;

$printer = new JsonPrettyPrinter(indent: '  ');

echo $printer->print($document);

Pretty output uses consistent indentation, one object property or array value per line, and no trailing newline.

A custom indent must contain only spaces or tabs; both printers throw an InvalidArgumentException for any other character so the output stays valid JSON.

Printer Interface

Both printers implement JsonPrinter:

use Boundwize\JsonRecast\Printer\JsonPrinter;

function render(JsonPrinter $printer, NodeJson $node): string
{
    return $printer->print($node);
}

Use JsonPreservingPrinter for source-to-source transformations. Use JsonPrettyPrinter for generated JSON or tests where stable normalized output is easier to assert.

Both printers accept a JsonDocument, a container, or a scalar node as the printed root. Item nodes only carry meaning inside their container, so printing an ObjectItemNode or ArrayItemNode directly throws a catchable RuntimeException such as ObjectItemNode cannot be printed as a JSON document. — a standalone object item would render as a bare "key": value fragment, which is not valid JSON. Traversal and the AST dumper still accept item subtrees.