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

Jsonq Laravel Package

nahid/jsonq

JsonQ is a PHP library for querying JSON data with a fluent, jq-like API. Traverse nested objects and arrays, filter with where, sort, select, and extract values without manual array handling. Ideal for APIs, config files, and dynamic JSON.

Deep Wiki
Context7

Getting Started

Install with Composer:

composer require nahid/jsonq

Point it at a JSON file and query it like a read-only table:

use Nahid\JsonQ\Jsonq;

$jsonq = new Jsonq(storage_path('app/exports/catalog.json'));

$products = $jsonq->from('products')          // dot-free node path inside the document
    ->where('price', '<=', 100)
    ->where('stock.available', true)          // dot notation for nested keys
    ->orderBy('price', 'asc')
    ->take(5)
    ->get();                                  // array of matching rows

Where to look first

  1. The README method table — it's the quickest way to see which of where, orWhere, whereIn, whereBetween, whereNull, whereStartsWith, whereContains, whereMatch, groupBy, orderBy, pluck, sum, avg, chunk, first, find your installed version actually ships.
  2. src/Jsonq.php — the operator switch inside the comparison method is the authoritative list; README and code drift on alias operators (eq, neq, gt, gte, lt, lte, notin, …).
  3. tests/ — fixtures plus assertions show the exact return shapes (array vs Collection) for get(), first(), and aggregate calls.

First real use case: reference data you don't want to model in a table — country/currency lists, a vendor's exported price list, seed data for a demo, or an API dump you're exploring before designing migrations. It's also handy in one-off Artisan commands and data-migration scripts where json_decode + manual foreach would otherwise sprawl.

Implementation Patterns

Wrap it in a repository, bind it once. The query object is stateful and the file decode is expensive, so keep one per request behind a small class:

final class CatalogRepository
{
    private ?Jsonq $jsonq = null;

    public function __construct(private string $path) {}

    private function q(): Jsonq
    {
        return $this->jsonq ??= new Jsonq($this->path);
    }

    public function cheapProducts(int $max = 100): array
    {
        return $this->q()->from('products')
            ->where('price', '<=', $max)
            ->get();
    }
}

Bind it in a service provider with storage_path('app/exports/catalog.json') so every caller uses an absolute path.

Whitelist request-driven filters. This is the pattern that shows up in admin screens over JSON exports:

$allowed = ['price', 'category', 'stock.available'];
$q = (new Jsonq($path))->from('products');

foreach ($request->only($allowed) as $field => $value) {
    $q->where($field, $value);
}

return response()->json($q->take(50)->get());

Aggregate instead of hydrating. If you only need a number, don't get() and loop — ->sum('total'), ->avg('price'), ->max('created_at'), ->count() keep the code short and avoid materializing a big array in your own scope.

Group and reduce with Collections. Mixing the two is the pragmatic sweet spot:

$byCategory = collect($jsonq->from('products')->get())
    ->groupBy('category')
    ->map(fn ($rows) => collect($rows)->sum('price'));

Chunk when iterating. ->chunk($size, $callback) (or collect($q->get())->chunk()) keeps your processing loop flat when you're writing rows to the DB or pushing to a queue. Note it still holds the whole document in memory — the win is in your own code, not the library.

Pipe remote payloads through disk. Fetch with the HTTP client, json_decode once to validate, write to storage_path('app/tmp/'.Str::uuid().'.json'), then query that file. Unique filenames avoid concurrent-request races; clean up in a finally.

Test with fixture files. new Jsonq(__DIR__.'/Fixtures/catalog.json') in a unit test is faster and more deterministic than mocking the filesystem, and it catches version-specific return-shape surprises. Wrap get() in collect() in the assertion if the shape ever changes.

Eloquent-adjacent use. The package ships a JsonQueriable trait in some versions for querying a JSON column/attribute with the same fluent API — check whether your locked version includes it before designing around it.

Gotchas and Tips

  • Relative paths resolve against the current working directory. The same string that works in php artisan serve fails in a queued job or scheduler. Always use storage_path()/base_path(). A missing file throws at construction, so validate in your repository constructor or a config check.
  • Types are strict where you least expect it. JSON "1" (string) will not match where('id', 1) (int). Empty result sets from a type mismatch are the number-one source of "the package is broken" reports — cast inputs and confirm the JSON's actual types with json_decode first.
  • The builder mutates itself. Conditions accumulate on the instance. Reusing one Jsonq object across two queries leaks the first query's where clauses into the second. Build one chain per query, or call from() again to reset — and don't share an instance across a loop without verifying reset behavior in your version.
  • There is no LIKE. Use whereContains, whereStartsWith, whereEndsWith, or whereMatch with a full regex including delimiters ('/^foo/i'). Passing SQL-style %foo% silently matches nothing.
  • Rows missing the filtered key are dropped. A filter on an optional field excludes records that don't have it, which usually isn't what you meant. Combine with whereNull in an orWhere chain, or post-filter the full result set.
  • No grouped boolean logic. orWhere chains evaluate left-to-right with no parentheses, so A AND (B OR C) requires two queries and a manual merge/unique().
  • first() returns null on no match — guard before array access; chaining onto a null result raises NullValueException.
  • Dot notation has no escape hatch. A literal key like "user.name" will be traversed as two levels. Rename such keys on ingest or use Illuminate\Support\Arr::get for those fields.
  • Result shape varies by version. Some releases return plain arrays from get(), others Illuminate\Support\Collection. Normalize at the repository boundary with collect()/->toArray() so a version bump doesn't ripple through controllers.
  • Memory and performance ceiling. The entire file is read and decoded in memory; there's no streaming, indexing, or partial read. Under ~10MB it's fine; beyond that, cache the decoded result (Cache::remember keyed on file mtime) or move to SQLite with json_extract. Instantiating new Jsonq(...) per query re-reads the file every time.
  • Maintenance risk. Last tagged release is February 2021, so verify against your PHP version (8.2+ deprecation notices are plausible) and pin the exact version in composer.lock. If you need PHP-version guarantees long-term, budget for a fork — the surface area is small and readable.
  • Debugging: there's no toSql(). Build the chain into a variable, then dump() after each step (or ->count() to check a filter narrowed as expected) to isolate which condition zeroes the result. If the answer is [], check path/node existence, key type, and operator alias in that order.
  • Sanitize anything user-supplied. You're not exposed to SQL injection, but unbounded take() and arbitrary field names from request params are still a resource and information-disclosure problem — whitelist both.
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.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo