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

Json Stream Polyfill Laravel Package

joelwurtz/json-stream-polyfill

A polyfill bringing JSON streaming support to older PHP versions, so large JSON payloads can be encoded and decoded incrementally without loading the whole document into memory. Drop-in compatibility for projects unable to upgrade PHP.

View on GitHub
Deep Wiki
Context7

Getting Started

Install it like any other Composer library:

composer require joelwurtz/json-stream-polyfill

The whole point of a polyfill is that you don't call it directly. It registers the json_stream extension's API (functions/classes in the global namespace) only when the real extension isn't loaded, so application code keeps targeting the extension and the polyfill transparently backfills it on machines that lack ext-json_stream (shared hosting, default Docker images, CI runners).

Where to look first:

  1. vendor/joelwurtz/json-stream-polyfill/src/ — confirm exactly which symbols it defines (function names, class names, constants). This is a 0-star package; the source is the documentation.
  2. Your php -m | grep json_stream output — if the extension is present, the polyfill is a no-op and you should verify that path separately.
  3. A one-line smoke test in php -r or a test case asserting function_exists(...)/class_exists(...) after autoload.

First use case to validate it with: a large JSON array of records that you currently json_decode() into memory. Something like a 200 MB export file, or an API response with 100k objects. Parse it lazily, foreach the records, write each one to the database, and confirm peak memory (memory_get_peak_usage(true)) stays flat instead of scaling with file size.

A sane composer.json posture:

{
    "require": {
        "joelwurtz/json-stream-polyfill": "^1.0"
    },
    "suggest": {
        "ext-json_stream": "Native implementation, significantly faster than the polyfill"
    }
}

Do not put ext-json_stream in require — the polyfill exists precisely so you don't have to, and adding it will break installs on hosts without the extension.

Implementation Patterns

Write against the extension API, treat the polyfill as an implementation detail. Your code should never reference the polyfill's namespace. If the extension is installed in production, you get the fast path for free; if not, the polyfill keeps the app working. Gate optional code paths with function_exists()/class_exists() rather than with a Composer platform check.

Wrap it in an adapter, not scattered calls. This is the single highest-value pattern for a package with this maturity level: you get a seam to swap implementations, measure them, and unit-test without depending on which one is loaded.

interface JsonRecordStream
{
    /** @return iterable<array> */
    public function records(string $path): iterable;
}

final class ExtensionOrPolyfillStream implements JsonRecordStream
{
    public function records(string $path): iterable
    {
        $handle = fopen($path, 'rb');
        try {
            foreach (json_stream_decode_stream($handle) as $record) { // extension API
                yield $record;
            }
        } finally {
            fclose($handle);
        }
    }
}

Consume lazily and stop early. Streaming only pays off if downstream code is also lazy. The canonical pipeline is: open stream → iterate records → transform → batch-insert / push downstream → never accumulate.

$batch = [];
foreach ($stream->records($path) as $record) {
    $batch[] = $record;
    if (count($batch) >= 500) {
        $repository->insertBatch($batch);
        $batch = [];
    }
}

Feed from HTTP bodies, not just files. Combine with a PSR-7 stream or a plain fopen('https://...') handle so you start processing the first records before the response has fully downloaded. This is where the win over json_decode(file_get_contents(...)) is most dramatic — both memory and time-to-first-record.

Route by payload size. For small documents the polyfill's pure-PHP tokenizer is pure overhead. A pragmatic dispatcher:

if ($size < 1_000_000) {
    return json_decode($contents, true, 512, JSON_THROW_ON_ERROR);
}
return $this->stream->records($path);

Test against both implementations. Run your unit tests in CI twice: one job with the PECL extension enabled, one without. Any divergence in output is a bug you want to find in CI, not in production. Keep a fixture corpus of awkward JSON (deep nesting, unicode escapes, empty arrays, numeric keys) and assert both paths produce identical structures.

Prefer it over hand-rolled regex/SABRE hacks. If you're currently doing explode('},{') on a giant string to avoid OOM, this is a cleaner replacement — same memory profile, correct parsing.

Gotchas and Tips

The memory win is conditional. The polyfill reduces parsing memory, but if you iterator_to_array() the result you've gained nothing. Audit the whole chain — loggers that serialize the payload, var_dump in error handlers, and collection wrappers quietly defeat streaming.

Performance is not comparable. Pure-PHP tokenization is typically an order of magnitude slower than the C extension, and slower than json_decode on documents small enough to fit in memory comfortably. Benchmark with realistic payloads before adopting; the polyfill is a compatibility tool first and a performance tool second.

Error semantics will diverge. Exception classes, messages, and behaviour on truncated/malformed input are the classic polyfill fault line. Don't assert on exception messages; assert on a wrapper type you throw yourself. Test malformed-JSON handling explicitly on both paths — a partially-parsed stream that silently yields half the records before failing is a real production hazard.

Edge-case flags may be unsupported. JSON_BIGINT_AS_STRING, depth limits, and JSON_THROW_ON_ERROR interactions are the usual suspects. If your data contains IDs above PHP_INT_MAX or deeply nested structures, verify the polyfill honours the same options before you trust it. Large integers silently becoming floats is the kind of bug that surfaces months later in a payments table.

Generators are single-pass. No rewind(), no count(), no seek(). Anything that needs two passes (validation then insert) requires either buffering to a temp file or restructuring into a single pass.

Autoload ordering matters. The polyfill defines global functions at autoload time. If you have code paths that use json_stream_* before Composer's autoloader is registered — front-controller bootstrap oddities, preloading configs, auto_prepend_file setups — you'll get a fatal "undefined function" that only appears on hosts without the extension. Verify with opcache.preload if you use it.

Debugging by differential testing. When output looks wrong, run the same input through json_decode() and compare structurally (assertEquals on the decoded array, not string comparison). Fuzz with generated JSON to find divergences fast. Also check php -m first — half of all "polyfill bug" reports turn out to be the native extension being loaded with different behaviour.

Extension points are minimal — build your own. Don't expect hooks or a plugin system. The right layering is: your adapter interface (above), plus decorators for metrics (records/sec, bytes/record), retry, and validation. Keep the polyfill at the bottom of the stack, unseen.

Vendor risk is low but non-zero. MIT-licensed, tiny, zero-dependency, zero-dependents, single maintainer, no release cadence to speak of. Pin the version, vendor it if your deploy pipeline allows, and be prepared to fork — the surface area is small enough that maintaining a fork is a day's work, not a quarter's.

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.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource