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

Jsonpath Laravel Package

softcreatr/jsonpath

A fast, eval-free JSONPath implementation for PHP 8.5+. Query arrays, objects, and ArrayAccess/traversables with unions, slices, wildcards, filters, regex, and deep equality. Includes tokenized parsing and internal caching.

View on GitHub
Deep Wiki
Context7

Getting Started

Require the package — 2.2.0 adds an opt-in RFC 9535 mode and findPaths(), so pin the minor version if you rely on those:

composer require softcreatr/jsonpath:^2.2

Instantiate the parser and extract nested data. The default mode preserves pre-2.2 behavior:

use Softcreatr\JsonPath\JsonPath;

$json = '{"users": [{"name": "Alice", "role": "admin", "email": "[email protected]"}]}';
$jp = new JsonPath();
$results = $jp->find($json, '$.users[*].name'); // ['Alice']

For standards-compliant parsing, opt in to RFC 9535 mode. This is a per-instance mode; it does not change existing call sites:

$jp = new JsonPath(JsonPath::RFC9535); // opt-in mode; see README if your version uses a setter
$results = $jp->find($json, '$.users[?match(@.email, "[a-z]+@example\\.com")]');
$paths = $jp->findPaths($json, '$.users[*].name'); // normalized RFC result paths

The first thing worth trying in 2.2.0 is findPaths() when you need to know which nodes matched, not just their values, and RFC 9535 mode for queries that must be portable or strictly validated:

$jp = new JsonPath(JsonPath::RFC9535);

$matches = $jp->find($json, '$.users[?search(@.name, "lic")]');
$paths = $jp->findPaths($json, '$.users[?length(@.name) > 4]');

Skim the README for selector syntax. The RFC 9535 function list (length, count, match, search, value) and RFC 9485 I-Regexp pattern rules still apply. There is still no Laravel service provider — see Gotchas for wiring it up.

Implementation Patterns

  • API Response Filtering: Extract only the records you need from third-party payloads (Stripe webhooks, GitHub events) with match()/search() in the path instead of array_filter over a fully decoded tree. Cheaper on memory for large payloads and keeps selection logic declarative.
  • Standards Mode for Portable Queries: When JSONPath expressions come from configuration, a query builder, or another service, construct the parser with JsonPath::RFC9535. This enables full standard query parsing, eager type validation, Unicode and escaped member names, mixed selector unions, and RFC filter and slice semantics. Keep legacy mode for existing path strings that depend on the package's historical behavior.
  • Auditing Matches with findPaths(): Use findPaths() when you need normalized RFC result paths, including ordered duplicate matches. This is the fastest way to debug a filter that returns more or fewer nodes than expected, and to feed match locations into logs or downstream processing.
  • Payload Validation Before Dispatch: Use length() and count() as preconditions in job handlers and listeners:
    $invalid = $jp->find($payload, '$.line_items[?count(@.taxes[*]) == 0]');
    if (! empty($invalid)) {
        throw new InvalidWebhookPayloadException();
    }
    
  • Pattern Validation in Form Requests: Replace ad-hoc preg_match calls in custom validation rules with match() for full-string validation of nested JSON fields. In RFC 9535 mode, invalid queries are rejected independently of document contents, so a malformed pattern fails during query validation instead of producing an empty result:
    $ok = $jp->find($payload, '$.customer[?match(@.email, "[^@]+@[^@]+\\.[a-z]{2,}")]') !== [];
    
  • Normalizing Single Values: value() collapses a nodelist to a scalar so comparisons don't need [0] juggling, which is handy when the same path may match one node or none:
    $jp->find($json, '$.meta[?value(@.version) == "2.2.0"]');
    
  • Data Transformation: Combine with collect() for mapping and plucking:
    $matches = $jp->find($json, '$.items[?(@.status == "active")]');
    $ids = collect($matches)->pluck('id')->all();
    
  • Config-Driven Path Templates: Store frequently used JSONPath expressions in config/ and inject them, rather than scattering string literals. Keep interpolated values (tenant ids, statuses) escaped/quoted — never interpolate raw user input into a filter.
  • Testing Helpers: Continue to use Laravel's assertJsonPath() for asserting a single known key; reach for find()/findPaths() when you need to assert on a filtered set or on which paths matched.
  • Service Binding: Bind a preconfigured JsonPath instance as a singleton in a service provider (e.g. AppServiceProvider::register()) and inject it into job handlers and clients. The parser is stateless and safe to reuse. If you need both modes, bind named instances (e.g. jsonpath and jsonpath.rfc9535) rather than flipping mode at call sites.

Gotchas and Tips

  • RFC 9535 Mode Is Opt-In and Isolated: Existing calls retain their previous behavior in 2.2.0. You must explicitly enable JsonPath::RFC9535. Don't assume two JsonPath instances parse the same path the same way — mode isolation is intentional and covered by the package's regression tests.
  • Mode Switching Can Change Query Results: RFC 9535 mode adds mixed selector unions, RFC filter and slice semantics, Unicode and escaped member names, and eager type validation. A path that works in legacy mode may be rejected in RFC mode, and vice versa. Test paths when migrating, especially filters that relied on lenient behavior.
  • Invalid Queries Are Rejected Before Document Evaluation: In RFC 9535 mode, invalid queries are rejected independently of document contents. This is useful for validating config at boot, but it means a bad path can throw even when the document is empty. Wrap paths sourced from config or user input in try/catch.
  • findPaths() Returns Normalized RFC Paths: Use findPaths() for normalized result paths, including ordered duplicate matches. It is not a drop-in replacement for findAll() when you need values alongside paths — keep using findAll() for value+path extraction and find() for values.
  • match() Is Full-String, search() Is Substring: This remains the biggest behavioral surprise. match() behaves like an anchored pattern and does not behave like PHP's unanchored preg_match(). If you're porting a preg_match('/foo/') check, you almost certainly want search(). Anchors (^, $) aren't the mechanism here — don't add them.
  • I-Regexp Is Not PCRE: RFC 9485 is a restricted dialect. No backreferences, no lookarounds, no named groups, and a narrower set of character-class escapes. Test every pattern you port from PCRE; some will simply be rejected by the parser.
  • Regex Resource Limits: The I-Regexp implementation enforces PCRE resource limits, so catastrophic patterns fail fast instead of hanging a worker. That failure surfaces as an exception — wrap any pattern that originates from configuration in a try/catch.
  • Stricter Expression Validation Is Still a Behavior Change: Since 2.1.0 function expressions have gone through a dedicated parser with a static type checker. 2.2.0 adds eager validation in RFC 9535 mode. Ill-typed expressions (a nodelist where a value is expected, comparing a logical result to a string, etc.) are rejected rather than silently returning an empty result. Code that relied on "bad filter → empty array" may throw — add try/catch around dynamic paths.
  • No PHP Evaluation: Malformed or ill-typed expressions are rejected before any user-controlled PHP code is evaluated, so JSONPath strings from outside are not an RCE vector. They are still a logic and DoS vector — whitelist acceptable paths/patterns, cap input size, and never string-concatenate untrusted values into a filter.
  • count() Counts Nodes, length() Counts Members: count() operates on a nodelist (@.items[*]); length() operates on a string, array, or object value. Using the wrong one — or calling length() on a nodelist — is a type error, not a silent null.
  • value() on Multiple Nodes Yields Nothing: A filter comparing value() of a multi-node nodelist simply doesn't match; it does not raise. Silent non-matches are the classic "my filter returns zero rows" cause — narrow the path first.
  • Empty Results Are Arrays: $jp->find($json, '$.missing') returns [], not null. Guard with empty($results), especially when the filter contains functions that legitimately match nothing.
  • Input Handling: The parser handles JSON strings, but decoding yourself gives you control over json_decode error handling and lets you cache the decoded structure. Repeated decoding is the usual performance culprit, not the matching itself.
  • Extension Tip: Extend \Softcreatr\JsonPath\JsonPath with domain helpers (e.g. findActiveUsers()) that pre-apply common filters, so I-Regexp patterns and type constraints live in one tested place. If you extend the class, decide explicitly whether helpers use default or RFC 9535 mode.
  • Performance: For payloads over ~1MB, decode once and reuse the instance; avoid regex filters over very large nodelists if a plain comparison (==) will do — match()/search() are convenient, not free.
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.
mehedimi/laravel-amp-mail
happones/laravel-vue-i18n-generator
jwerd/inertia-table
siberfx/flysystem-wasabi
hollyit/token-replace
ippanelcom/laravel-sdk
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel