clue/json-stream
Streaming JSON parser and encoder for PHP that reads and writes large JSON documents incrementally, without loading the whole payload into memory. Built on ReactPHP streams, it handles huge files and endless data feeds efficiently via a simple event-driven API.
Architecture fit. clue/json-stream is a push-based, incremental JSON parser built on ReactPHP's stream abstractions (react/stream, evenement). It attaches to a ReadableStreamInterface and emits a data event per decoded JSON document, supporting both concatenated JSON ({...}{...}) and newline-delimited JSON. That is a fundamentally asynchronous programming model, while Laravel is request-scoped and synchronous. The package therefore fits only in the long-running fringes of a Laravel application: artisan commands, queue workers, Octane workers, or a dedicated ingest process. It does not belong on the HTTP request path unless the team is already comfortable running an event loop inside a controller.
Integration feasibility. Install is trivial (composer require clue/json-stream), MIT-licensed, and the codebase is small enough (~a parser plus a stream decoder) to vendor or fork if needed. The real gating item is platform currency: the last release was 2020-12-19, and the package was never CI-tested against PHP 8.1–8.3. Since Laravel 10/11 requires PHP 8.1+, we must verify installability and runtime behavior on our target PHP version before committing. Transitive dependencies are light (react/stream, evenement/evenement), but they introduce ReactPHP into the dependency graph, which is a durable architectural commitment, not a drive-by utility.
Technical risk.
json_decode on that record.\uXXXX, BOM, CRLF, whitespace between documents) is mandatory.Key questions.
halaxa/json-machine (synchronous generator-based streaming, Laravel-friendly, far larger community) against this use case? If not, why is an async parser the default answer?Stack fit. Best suited to Laravel Octane or standalone long-running workers where an event loop already exists or can be owned. For classic PHP-FPM deployments, it runs fine inside an artisan command or a queued job that calls Loop::run(), but the loop must be short-lived and exception-safe so it never leaks into the next job.
Recommended pattern — adopt behind a port. Do not let clue/json-stream types bleed into domain code. Define an internal contract, e.g. App\Contracts\JsonStreamReader, with a single method returning an iterable/generator of decoded documents plus a progress/offset reporter. Provide two adapters:
JsonMachineReader (synchronous, generator-based, zero new dependencies) as the default.ClueStreamReader (ReactPHP) for sources that are genuinely push-based or already async.This keeps the vendor decision reversible and makes the eventual migration a one-line binding change.
Compatibility details to nail down in the spike.
composer install on PHP 8.2/8.3 with Laravel 10/11 — check for composer.json platform constraints and deprecation notices at runtime.React\Stream\ReadableResourceStream; S3 via Storage::readStream() piped into a readable stream; gzip via PHP's zlib.inflate stream filter rather than post-hoc decompression.Sequencing.
JsonStreamReader; implement the json-machine adapter first, clue adapter second.Maintenance. Treat this as a "vendor-and-own" dependency: no upstream roadmap, no security response guarantee. Budget a named owner, pin an exact version, and keep a short fork playbook (the code is small enough that a fork is a half-day, not a project). Re-evaluate annually against json-machine's trajectory.
Support. Community-only, MIT, no SLA. Escalation is us. This is acceptable for a batch pipeline, harder to justify for a customer-facing path.
Scaling. Single-threaded and push-based; effective throughput is bounded by the slowest consumer, typically Eloquent inserts. Plan to batch writes (e.g. 500-record chunks with insert() or upsert()) inside the data callback, and to partition large inputs by file or byte range so work parallelizes across workers. Memory is the primary scaling constraint: high-water mark scales with the largest decoded document plus any un-consumed queue, so instrument memory_get_peak_usage() per run and alert on regressions.
Failure modes to design for.
finally block; a leaked loop is a slow-motion memory leak across requests.Ramp-up. A mid-to-senior PHP engineer needs roughly one to two days to become productive, most of which is the ReactPHP mental model (event emitters, stream lifecycle, Loop::run() boundaries) rather than the parser itself. Document the "never call this on the request path" rule explicitly, because that is the most likely and most expensive misuse.
Verdict. Conditional go. Recommended only where the source is genuinely push-based or ReactPHP is already present; otherwise the synchronous, far-better-supported halaxa/json-machine should be the default, with this package evaluated in a bake-off during the spike. The deciding evidence is a measured throughput/memory comparison on a representative 1 GB fixture, not the star count or the description.
How can I help you explore Laravel packages today?