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 Parser Laravel Package

cerbero/json-parser

A PHP/Laravel package for parsing large JSON files efficiently with low memory. Use JSONPath-style queries to extract exactly the data you need, stream huge documents without loading them fully, and integrate cleanly into Laravel apps for imports, APIs, and data processing.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package with Composer:

composer require cerbero/json-parser

It is framework-agnostic — no service provider or Laravel config is required. Check composer.json for the exact PHP constraint before adopting it, then start with the package README and src/JsonParser.php; the source is small and the tests are the best documentation for pointer and source behaviour.

Point it at any JSON source — a file path, a URL, an open stream resource, or a raw JSON string:

use Cerbero\JsonParser\JsonParser;

foreach (JsonParser::parse(storage_path('app/export.json')) as $key => $item) {
    // One item is held in memory at a time.
}

Your first real use case will almost always be: "I have a large JSON export and I need to import it without blowing up memory_limit." Write a throwaway Artisan command, point the parser at the branch that contains the records, and insert them into the database in chunks.

Implementation Patterns

Point at the payload branch, not the whole document

Most APIs wrap records in metadata. Use a JSON Pointer to skip straight to the array you care about:

$items = JsonParser::parse($path)->pointer('/data/items');

foreach ($items as $item) {
    // $item is the decoded record
}

This also avoids decoding sibling metadata you never use.

Chunked database writes

Do not call Model::create() per row. Buffer and flush:

$buffer = [];

foreach (JsonParser::parse($path)->pointer('/data/items') as $item) {
    $buffer[] = [
        'external_id' => $item['id'],
        'name'        => $item['name'],
        'created_at'  => now(),
        'updated_at'  => now(),
    ];

    if (count($buffer) >= 500) {
        DB::table('items')->upsert($buffer, ['external_id'], ['name', 'updated_at']);
        $buffer = [];
    }
}

if ($buffer) {
    DB::table('items')->upsert($buffer, ['external_id'], ['name', 'updated_at']);
}

Use upsert when the import may be re-run. Streaming parsers have no resume cursor, so idempotency is your recovery strategy.

Progress reporting in an Artisan command

Long imports need feedback. Feed the parser's progress callback into your command output:

$parser = JsonParser::parse($path)->pointer('/data/items');

$parser->progress(function ($progress) {
    $this->output->write(sprintf(
        "\r%d / %d bytes",
        $progress->bytes(),
        $progress->total()
    ));
});

Keep a --dry-run flag that iterates but skips writes; it validates both the JSON and your field mapping cheaply.

Wrap parsing in a service or generator

Keep pointer strings and row mapping out of controllers and jobs. A generator-based importer makes the streaming nature explicit:

final class ItemImporter
{
    /** @return \Generator<int, array> */
    public function rows(string $path): \Generator
    {
        $parser = JsonParser::parse($path)->pointer('/data/items');

        foreach ($parser as $item) {
            yield ItemData::fromArray($item);
        }
    }
}

Callers can then chunk, count, or test the importer with a small fixture without knowing anything about the parser.

Queue job for large files

Process a file in a dedicated job, keep chunk size modest, and set job/worker timeouts explicitly:

final class ImportJsonFile implements ShouldQueue
{
    public int $timeout = 900;
    public int $tries = 1;

    public function __construct(public string $path) {}

    public function handle(): void
    {
        // chunked upsert loop as above
    }
}

Prefer upserts over "delete then insert" so a failed or retried job is safe.

Sources and stream context

JsonParser::parse() accepts paths, URLs, resources, and strings. For authenticated or customised HTTP sources, open the stream yourself and pass the resource — that keeps headers, timeouts, and TLS options under your control:

$context = stream_context_create([
    'http' => [
        'header' => "Authorization: Bearer {$token}\r\n",
        'timeout' => 30,
    ],
]);

$stream = fopen('https://api.example.com/export.json', 'rb', false, $context);

foreach (JsonParser::parse($stream)->pointer('/data/items') as $item) {
    // ...
}

fclose($stream);

Testing

Fixtures do not need to be large. Test pointer selection, missing keys, and malformed JSON with small inline strings:

$parser = JsonParser::parse('{"data":{"items":[{"id":1}]}}')->pointer('/data/items');

Gotchas and Tips

  • Memory only stays flat if you keep it flat. Never do collect($parser) or build a full array of results "for later". Yield, buffer in fixed-size chunks, and discard.
  • JSON Pointer syntax matters. Pointers are slash-prefixed (/data/items); escaped characters follow JSON Pointer rules (~0 for ~, ~1 for /). A wrong pointer is a common cause of "it parsed but yielded nothing".
  • Errors surface mid-iteration. Malformed JSON throws while you are already inside the foreach. Wrap the loop in try/catch and decide whether a partially imported file should be rolled back or upserted over.
  • Streams are forward-only. You cannot rewind and iterate twice without re-parsing or reopening the source. If you need two passes, download locally first or parse twice deliberately.
  • Remote sources are fragile. URL parsing depends on allow_url_fopen and network stability. For anything mission-critical, download to a temp file, validate, then parse locally. Progress "total" is also unreliable for non-seekable remote streams.
  • Do not assume decoded objects. Records arrive as PHP arrays; missing keys and null values need explicit handling. Add validation or DTO construction at the boundary rather than deep inside the loop.
  • Progress callbacks are for humans, not control flow. Reporting % is fine; using it to decide chunk boundaries or cancellation is not. Track your own row counter.
  • Chunk inserts inside transactions carefully. A 500-row chunk in a transaction is fine; wrapping a multi-million-row stream in one transaction is not. Flush and commit per chunk.
  • Queue workers need realistic limits. A streaming import can run for a long time even with flat memory. Set $timeout on the job, run a worker with a compatible --timeout, and monitor failures.
  • Keep the dependency behind your own class. The package is framework-agnostic, stable but infrequently released, and has no Laravel-specific sugar. Pin the version, wrap JsonParser::parse() in one importer/service, and add a small integration test with a fixture — that makes swapping or patching the parser later a one-file change.
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.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core