Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Jsonreader Laravel Package

pcrov/jsonreader

A streaming, pull-based JSON parser for PHP. Process large documents incrementally from strings or streams without loading everything into memory. Iterate over tokens and events with a simple, readable API—ideal for big files and memory-constrained environments.

Deep Wiki
Context7

Getting Started

pcrov/jsonreader is a plain library — there is no service provider, facade, or config to publish. Install it and you're done:

composer require pcrov/jsonreader

Read the package README's usage block first, then the JsonReader class itself; the public surface is small (open/json/read/close, plus name/type/value/depth and the navigation methods skip/next). Skip everything else until you need it.

The mental model is a forward-only cursor over a token stream, not a DOM:

use pcrov\JsonReader\JsonReader;

$reader = new JsonReader();
$reader->open(storage_path('app/export.json'));   // or $reader->json($string)

while ($reader->read()) {
    printf(
        "%s%s => %s\n",
        str_repeat('  ', $reader->depth()),
        (string) $reader->name(),
        var_export($reader->value(), true)
    );
}

$reader->close();

First real use case in a Laravel app: an export/import job that currently does json_decode(file_get_contents(...)) on a file too big for memory. Swap the decode for the loop above, filter on $reader->type() and $reader->depth(), and you've cut peak memory from "whole document" to roughly "one token".

Implementation Patterns

1. Streaming the top-level array of records. The most common shape in app code — a giant array of objects:

$reader = new JsonReader();
$reader->open($path);

try {
    $reader->read();                       // enter the outer [
    while ($reader->read() && $reader->depth() === 1) {
        if ($reader->type() === JsonReader::OBJECT) {
            yield $this->collectObject($reader);   // one record at a time
        }
    }
} finally {
    $reader->close();
}

Wrap this in a Generator and a small service class so jobs and commands share it. Always close() in finally — the reader holds an open handle, and queue workers are long-lived.

2. Collect only the fields you need, skip() the rest. skip() jumps past the current container's children; use it to avoid paying for subtrees you'll throw away:

$depth = $reader->depth();
$record = [];

while ($reader->read() && $reader->depth() > $depth) {
    if ($reader->depth() === $depth + 1 && !in_array($reader->type(), [JsonReader::OBJECT, JsonReader::ARRAY], true)) {
        $record[$reader->name()] = $reader->value();
    } elseif ($reader->type() === JsonReader::OBJECT || $reader->type() === JsonReader::ARRAY) {
        $reader->skip();                   // nested blob we don't care about
    }
}

This "flatten to scalars, skip containers" loop is the workhorse pattern; almost every consumer boils down to it.

3. Filtering while scanning. For a "find the one record with id X" task you never build the array at all: read to the record's OBJECT token, inspect fields until you see a mismatch, skip(), move on. Constant memory regardless of file size.

4. Fast-fail validation of uploads. Before storing a user-uploaded JSON file, run a cheap full scan with an empty body — it throws on malformed input and costs one pass. Much better UX than discovering truncation days later in a downstream job.

5. Streams instead of paths. open() takes anything fopen() accepts, so Storage::readStream(), php://stdin, and compress.zlib://…gz all work. Great for php artisan import piping from another process, and it means you rarely need a temp file.

6. Integration tip — don't hide it behind a repository. Because it's a cursor, it composes badly with Eloquent-style lazy collections and doesn't survive serialization. Keep it inside a job/command, hand each record to your normal action/DTO layer, and let the framework handle persistence.

Gotchas and Tips

  • value() is for scalars. On OBJECT/ARRAY tokens don't expect a PHP array — you're standing at the opening brace. Branch on type() and descend.
  • No rewind, ever. There is no seek or "read this path again". If you need two passes, open() twice (or restructure so one pass yields what both consumers need). Calling open()/json() resets the reader mid-stream — useful, but silently discards state.
  • Errors are lazy. A syntax error at byte 900 MB surfaces only when you read() past it, i.e. after you may have already imported half a million records. Either wrap the loop in a transaction/savepoint, or pre-validate small-to-medium files with json_decode (which is also faster there).
  • UTF-8 BOM. A leading \xEF\xBB\xBF (common in files from Excel/Windows tooling) can blow up the first read(). Strip it when you open the stream, and normalize encoding before parsing.
  • Empty/whitespace-only input is not an error. read() just returns false immediately — which looks identical to a successful zero-token parse. Treat "zero tokens read" as a malformed upload in your validation path.
  • Number fidelity. Large integers can arrive as floats and lose precision; very precise decimals do too. For snowflake IDs or money, consider reading as string (validate/quote upstream) or accept DOUBLE and cast deliberately. Note that JsonReader::INT vs JsonReader::DOUBLE is your only signal.
  • name() semantics differ by container. Object members report their key; array elements report an integer index; the root reports null. Don't assume string — var_dump() it once against your fixture before writing string-typed code.
  • depth() is the cheapest router you have. depth() === 1 for records inside a top-level array is a robust guard; hard-coded paths break the moment the export wrapper changes.
  • skip() vs next(). skip() abandons the children of the current container (cursor lands on its end token); next() advances to the next sibling. Mixing them up is the classic source of "I read every other record" bugs.
  • Don't route small payloads through it. Per-token overhead means json_decode() wins comfortably for anything under a few MB — and gives you a JsonException with a message you can show a user. Use the pull parser specifically for the big/streamed cases.
  • It is a parser, not a validator. Well-formedness is checked as you go; semantic expectations (required keys, types) are yours to enforce per record.
  • Debugging trick. Add a one-line tracer (depth/type/name/value) behind a --verbose flag, or assert token sequences in a feature test against a small fixture. Token streams are trivially snapshot-testable, which is the fastest way to catch a skip() regression.
  • Reuse across requests: bind a factory, not a singleton. A JsonReader is stateful and not safe to share between concurrent consumers; instantiate per stream, close in finally.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib