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

keboola/json-parser

A PHP library from Keboola for parsing JSON into PHP data structures, with configurable options for handling JSON strings and files. Lightweight and straightforward to integrate into existing PHP projects.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and confirm compatibility first.

composer require keboola/json-parser

No service provider, facade, or config file ships with it. Before writing code, check the resolved package's composer.json for its PHP constraint — older Keboola releases lag behind current Laravel 11 / PHP 8.3 combinations, and the transitive keboola/php-temp and keboola/php-utils will pin your composer.lock in ways you want to see up front.

Read the source, not the README. The README is a sketch. The real contract lives in four classes under vendor/keboola/json-parser/src:

  • Parser — the orchestrator. parse($jsonString) then getCsvFiles(). It accepts a PSR-3 logger in its constructor.
  • Structure — the resulting tree of tables (root object as one table, each nested array as a child table).
  • File — one CSV output: getPath(), getFilename(), getContent(), getHeader().
  • Analyzer / Strategy — the type-inference and per-type serialization rules. This is where the "how will a value be rendered" answers live.

Bind it once so the logger is consistent. Illuminate\Log\Logger is PSR-3-compatible, so it plugs straight in:

// App\Providers\AppServiceProvider::register()
$this->app->singleton(\Keboola\Json\Parser::class, fn () =>
    new \Keboola\Json\Parser(
        \Illuminate\Support\Facades\Log::channel('single'),
    )
);

First use case — a downloadable export from a nested API payload. The typical day-one job is: fetch JSON from an API (or read a stored blob), flatten it, hand the CSVs to the user or to Storage:

use Keboola\Json\Parser;

$parser = app(Parser::class);
$parser->parse($jsonString);   // a string, not a path, not a stream, not an array

foreach ($parser->getCsvFiles() as $file) {
    // $file->getFilename()  e.g. "data.csv", "data_items.csv"
    // $file->getHeader()    column array
    // $file->getContent()   full CSV text incl. header
    Storage::disk('local')->put("exports/{$file->getFilename()}", $file->getContent());
}

If you start here, put this straight into an Artisan command or a ShouldQueue job — not a controller. See the next section for why.

Implementation Patterns

Always wrap it behind an interface. This is the single most important pattern for this package. Define a contract in your app and treat Keboola\Json\Parser as a swappable adapter:

namespace App\Contracts;

interface JsonToCsvExporter
{
    /** @return array<string, string> filename => CSV content */
    public function export(string|array $json, ExportOptions $options): array;
}

Then KeboolaJsonToCsvExporter implements JsonToCsvExporter is the only file that imports the vendor namespace. When the flattening semantics don't fit, or upstream goes quiet, you swap one class — not dozens of call sites. Bind the contract in your provider; inject the contract in callers.

Run it in queued jobs / console commands, never in a request. The library is file-oriented: it writes to a temp directory and hands back File objects. That is a poor fit for the request/response path — long payloads block the worker, and temp-file failures become 500s. The natural pattern:

class ExportJsonToCsv implements ShouldQueue
{
    public function __construct(public string $exportId, public string $json) {}

    public function handle(JsonToCsvExporter $exporter): void
    {
        $files = $exporter->export($this->json, ExportOptions::default());
        foreach ($files as $name => $csv) {
            Storage::disk('local')->put("exports/{$this->exportId}/{$name}", $csv);
        }
    }
}

Bridge to Laravel Storage explicitly. File::getPath() gives you a real filesystem path — great for Storage::path(), useless for S3/Flysystem disks directly. Pattern: let the library write locally (a directory under storage/app/private/exports is a fine staging area), then Storage::disk('s3')->put(...) the contents, and clean up local files in a finally block or the job's failed() handler.

Golden-file test the actual output. Because the flattening and type-coercion rules are opinionated and documented only in code, you cannot assume what a field will look like at the consumer. Take three or four representative production payloads, run them through the exporter, and commit the resulting CSVs as fixtures. Assert against them in tests. This is cheaper than reading the Analyzer/Strategy source and it turns the semantics into an executable contract the whole team can trust.

Config, not scattered literals. Even though the package ships no config, add your own (delimiter, enclosure, temp path, whether to coerce types or stringify everything, whether to sanitize formula-injection prefixes). Read it inside the adapter, not at call sites. This is what lets you later move off the package without changing consumers.

Version and pin deliberately. "keboola/json-parser": "^X.Y" in composer.json, and let composer.lock capture the transitive keboola/php-temp / keboola/php-utils. Treat every minor bump as a manual review — the flattening output can change between them, and silent header changes break downstream imports.

Gotchas and Tips

It is a JSON→CSV flattener, not a "nicer json_decode". Despite the summary phrasing, the day-to-day leverage is table conversion, not exception ergonomics. If all you need is clear error messages on json_decode, this dependency isn't worth it — Illuminate\Support\Arr + json_decode($s, true, flags: JSON_THROW_ON_ERROR) gets you that in one line. Adopt it because you want its flattening semantics.

Nested arrays spawn child tables with generated names. Root object → data.csv, each array in the object → data_<key>.csv, with a generated parent-link column. Your consumers will see those filenames and link columns. Agree the naming contract with whoever opens the CSVs before you ship; renaming later means regenerating every downstream integration.

Dot-notation keys and column explosion. Nested objects are flattened to address.city, address.zip style headers. Deeply nested or heterogeneous payloads produce wide, unstable column sets — one payload with an extra field changes the schema for everyone. If the shape is unstable, either pre-normalize the JSON before handing it to the parser, or accept that the flattener isn't the right tool.

Type inference is opinionated and lossy. The Analyzer/Strategy classes will infer numbers, booleans, dates. That means:

  • Leading-zero strings like 007 become 7 in the CSV.
  • Large integers (IDs, account numbers) can lose precision or get reformatted.
  • Dates and locales get normalized in ways you may not expect.

If your consumers need strings-exactly-as-received, sanitize the payload upstream or plan to replace the analyzer — which is the point at which building your own flattener becomes cheaper than fighting it.

CSV formula injection is your responsibility. Anything beginning with =, +, -, @ will be interpreted by Excel. If humans open these files, prefix-escape those cells in your adapter before storage. The package will happily write them through.

Temp directory assumptions will bite you in containers. The pipeline writes to sys_get_temp_dir() by default; in a container without a sized tmpfs or with a read-only tmp, it fails, and it fails after work has been done. Either point it explicitly at storage/app/private/tmp/... (and ensure the directory exists and is writable) or size and monitor the temp volume. Add a scheduled cleanup for orphaned files — a crashed worker leaves them behind.

Write-then-move, not write-in-place. A crashed worker mid-write leaves a truncated CSV that looks valid to the next step. Have your adapter write to a temp name and rename() into place atomically. Pair with a dead-letter queue for the Throwable path.

Memory is proportional to the JSON document. It decodes the whole thing. Chunk large arrays by record upstream (parse the top-level array yourself, feed the library per-chunk, concatenate) rather than handing it a 200 MB blob. Horizontally, you scale by adding queue workers, not by tuning the library.

Logging goes through the injected PSR-3 logger. Bind a dedicated single channel so parser warnings don't drown in your application log — and so you can grep them when an export silently produces the wrong number of rows.

Onboarding cost is the pipeline, not the library. A mid-level engineer is productive with parse() + getCsvFiles() in an afternoon. The hard parts — queue wiring, temp lifecycle, storage bridging, downstream schema, type overrides — are all in your app. Write the one-page runbook covering output filenames, temp locations, known flattening quirks, and the "swap this class" escape hatch, or the semantics will live only in whoever did the spike.

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.
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums