RFC 9535

JSONPath: Query Expressions for JSON

๐Ÿ“… published February 2024, from ietf

Part 1 of 14

JSONPath: Query Expressions for JSON ๐Ÿ’–

Okay bestie, here's the lineup ๐ŸŽ€ This is RFC 9535, a Standards Track document dated February 2024 with ISSN 2070-1721. The editors are S. Gรถssner (Ed.) from Fachhochschule Dortmund, G. Normington (Ed.), and C. Bormann (Ed.) from Universitรคt Bremen TZI. The title block doesn't list anything it obsoletes or updates, so there's nothing to flag there ๐Ÿงš

Abstract โœจ

JSONPath is a string syntax for picking out and pulling values from inside a JSON value. JSON itself is defined in RFC 8259 (JSON (RFC 8259)).

Status of This Memo ๐Ÿ“œ

This is an Internet Standards Track document ๐Ÿ”ฅ

It's a product of the Internet Engineering Task Force (IETF), so it represents the consensus of the IETF community. It went through public review and got approved for publication by the Internet Engineering Steering Group (IESG). If you want more on Internet Standards, check Section 2 of RFC 7841.

For the current status, any errata, and how to send feedback, go to https://www.rfc-editor.org/info/rfc9535.

Copyright (c) 2024 IETF Trust and the persons identified as the document authors. All rights reserved.

This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) as in effect on the date of publication. Please actually read those, because they spell out your rights and restrictions for this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions, and they come without warranty, as described in the Revised BSD License.

Table of Contents ๐ŸŒธ

1.  Introduction
  1.1.  Terminology
    1.1.1.  JSON Values as Trees of Nodes
  1.2.  History
  1.3.  JSON Values
  1.4.  Overview of JSONPath Expressions
    1.4.1.  Identifiers
    1.4.2.  Segments
    1.4.3.  Selectors
    1.4.4.  Summary
  1.5.  JSONPath Examples
2.  JSONPath Syntax and Semantics
  2.1.  Overview
    2.1.1.  Syntax
    2.1.2.  Semantics
    2.1.3.  Example
  2.2.  Root Identifier
    2.2.1.  Syntax
    2.2.2.  Semantics
    2.2.3.  Examples
  2.3.  Selectors
    2.3.1.  Name Selector
      2.3.1.1.  Syntax
      2.3.1.2.  Semantics
      2.3.1.3.  Examples
    2.3.2.  Wildcard Selector
      2.3.2.1.  Syntax
      2.3.2.2.  Semantics
      2.3.2.3.  Examples
    2.3.3.  Index Selector
      2.3.3.1.  Syntax
      2.3.3.2.  Semantics
      2.3.3.3.  Examples
    2.3.4.  Array Slice Selector
      2.3.4.1.  Syntax
      2.3.4.2.  Semantics
      2.3.4.3.  Examples
    2.3.5.  Filter Selector
      2.3.5.1.  Syntax
      2.3.5.2.  Semantics
      2.3.5.3.  Examples
  2.4.  Function Extensions
    2.4.1.  Type System for Function Expressions
    2.4.2.  Type Conversion
    2.4.3.  Well-Typedness of Function Expressions
    2.4.4.  length() Function Extension
    2.4.5.  count() Function Extension
    2.4.6.  match() Function Extension
    2.4.7.  search() Function Extension
    2.4.8.  value() Function Extension
    2.4.9.  Examples
  2.5.  Segments
    2.5.1.  Child Segment
      2.5.1.1.  Syntax
      2.5.1.2.  Semantics
      2.5.1.3.  Examples
    2.5.2.  Descendant Segment
      2.5.2.1.  Syntax
      2.5.2.2.  Semantics
      2.5.2.3.  Examples
  2.6.  Semantics of null
    2.6.1.  Examples
  2.7.  Normalized Paths
    2.7.1.  Examples
3.  IANA Considerations
  3.1.  Registration of Media Type application/jsonpath
  3.2.  Function Extensions Subregistry
4.  Security Considerations
  4.1.  Attack Vectors on JSONPath Implementations
  4.2.  Attack Vectors on How JSONPath Queries Are Formed
  4.3.  Attacks on Security Mechanisms That Employ JSONPath
5.  References
  5.1.  Normative References
  5.2.  Informative References
Appendix A.  Collected ABNF Grammars
Appendix B.  Inspired by XPath
  B.1.  JSONPath and XPath
Appendix C.  JSON Pointer
Acknowledgements
Contributors
Authors' Addresses

1. Introduction ๐Ÿฆ‹

JSON [RFC8259] is a popular format for representing structured data values, and it's basically everywhere. JSONPath gives you a string syntax for selecting and extracting JSON values from within a given JSON value. Main character energy ๐Ÿ‘‘

Compared to JSON Pointer [RFC6901], JSONPath isn't meant to replace it. Think of it as a more powerful companion instead ๐Ÿซถ See Appendix C for that comparison.

1.1. Terminology ๐Ÿ“š

Here's the key-word situation ๐Ÿ”ฅ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here. Lowercase versions don't count, so don't go reading a lowercase "may" as a requirement ๐Ÿ™ˆ

The grammar rules in this document are ABNF, as described in [RFC5234]. ABNF terminal values here define Unicode scalar values, not their UTF-8 encoding. For example, the Unicode PLACE OF INTEREST SIGN (U+2318) would be defined in ABNF as %x2318. Iconic ๐Ÿงš

Functions are written as the function name followed by a pair of parentheses, like fname() โœจ

The terminology of [RFC8259] applies, except where this document clarifies it. The terms "primitive" and "structured" group the different kinds of values, as in Section 1 of [RFC8259]. JSON objects and arrays are structured, and everything else is primitive. The definitions of "object", "array", "number", and "string" stay the same. Super important: "object" and "array" do not take on a generic programming meaning here, so don't bring that baggage in ๐Ÿ™…โ€โ™€๏ธ

The terminology of [RFC9485] applies too. Additional terms used in this document are defined below ๐Ÿ’œ

  • Value: As per [RFC8259], a data item that conforms to the generic data model of JSON. That means primitive data (numbers, text strings, and the special values null, true, and false) or structured data (JSON objects and arrays). [RFC8259] focuses on the textual representation of JSON values and doesn't fully define the value abstraction assumed here.
  • Member: A name/value pair in an object. (A member is not itself a value.)
  • Name: The name (a string) in a name/value pair that makes up a member. [RFC8259] uses this term too, but that spec doesn't formally define it. It's included here for completeness.
  • Element: A value in a JSON array.
  • Index: An integer that identifies a specific element in an array.
  • Query: Short name for a JSONPath expression.
  • Query Argument: Short name for the value a JSONPath expression is applied to.
  • Location: The position of a value within the query argument. You can think of it as a sequence of names and indexes that navigates to the value through the objects and arrays in the query argument, with the empty sequence meaning the query argument itself. A location can be represented as a Normalized Path (defined below).
  • Node: The pair of a value along with its location within the query argument.
  • Root Node: The unique node whose value is the entire query argument.
  • Root Node Identifier: The expression $, which refers to the root node of the query argument.
  • Current Node Identifier: The expression @, which refers to the current node in the context of evaluating a filter expression (described later).
  • Children (of a node): If the node is an array, the nodes of its elements. If the node is an object, the nodes of its member values. If the node is neither an array nor an object, it has no children.
  • Descendants (of a node): The children of the node, plus the children of those children, and so on recursively. More formally, the "descendants" relation between nodes is the transitive closure of the "children" relation.
  • Depth (of a descendant node within a value): The number of ancestors the node has within the value. The root node of the value has depth zero, its children have depth one, their children have depth two, and so forth.
  • Nodelist: A list of nodes. A nodelist can be represented in JSON, e.g., as an array, but this document doesn't require or assume any particular representation.
  • Parameter: A formal parameter (of a function) that can take a function argument (an actual parameter) in a function expression.
  • Normalized Path: A form of JSONPath expression that identifies a node in a value by giving a query that results in exactly that node. Each node in a query argument is identified by exactly one Normalized Path (we say that the Normalized Path is "unique" for that node). To be a Normalized Path for a specific query argument, the Normalized Path needs to identify exactly one node. It's similar to, but syntactically different from, a JSON Pointer [RFC6901]. Note: This definition is based on the syntactical definition in Section 2.7. JSONPath expressions that identify a node in a value but don't conform to that syntax are not Normalized Paths.
  • Unicode Scalar Value: Any Unicode [UNICODE] code point except high-surrogate and low-surrogate code points. In other words, integers in the inclusive base 16 ranges 0 to D7FF or E000 to 10FFFF. JSONPath queries are sequences of Unicode scalar values.
  • Segment: One of the constructs that selects children ([<selectors>]) or descendants (..[<selectors>]) of an input value.
  • Selector: A single item within a segment that takes the input value and produces a nodelist made of child nodes of the input value.
  • Singular Query: A JSONPath expression built from segments that have been syntactically restricted in a certain way (Section 2.3.5.1), so that, regardless of the input value, the expression produces a nodelist containing at most one node. Note: JSONPath expressions that always produce a singular nodelist but don't conform to the syntax in Section 2.3.5.1 are not singular queries.

1.1.1. JSON Values as Trees of Nodes ๐ŸŒณ

This document models the query argument as a tree of JSON values, and each value gets its own node. A node is either the root node or one of its descendants.

The result of applying a query to the query argument is modeled as a nodelist (a list of nodes) ๐Ÿ’–

Nodes are the selectable parts of the query argument. The only parts of an object a query can select are the member values. Member names and members (name/value pairs) can't be selected. So member values have nodes, but members and member names don't. Similarly, member values are children of an object, but members and member names aren't. Easy to mix up, so keep this one in mind! ๐Ÿ™ˆ

1.2. History โœจ

okay so this document is based on Stefan Gรถssner's popular JSONPath proposal (dated 2007-02-21) [JSONPath-orig] ๐Ÿ’–. It builds on the experience from the widespread deployment of its implementations, and it provides a normative specification for it. The vibes are immaculate ๐Ÿ‘‘.

Appendix B describes how JSONPath was inspired by XML's XPath [XPath] ๐Ÿฆ‹.

JSONPath was meant to be a lightweight companion to JSON implementations in programming languages like PHP and JavaScript ๐ŸŒธ. So instead of inventing its own expression language the way XPath did, it handed parts of a query off to the underlying runtime, e.g., JavaScript's eval() function. As JSONPath showed up in more environments, JSONPath expressions got decreasingly portable ๐Ÿ˜ญ. For example, regular expression processing was often delegated to a convenient regular expression engine.

This document is here to fix that ๐Ÿซถ. It aims to remove those implementation-specific dependencies and serve as a common JSONPath specification that works across programming languages and environments. The tradeoff is that backwards compatibility is not always achieved. The design principle is to go with a "consensus" between implementations, even if it's rough, as long as that doesn't jeopardize the goal of getting a usable, stable JSON query language ๐ŸŽ€.

The term JSONPath was chosen because of the XPath inspiration, and also because the outcome of a query consists of paths identifying nodes in the JSON query argument ๐Ÿงš.

1.3. JSON Values ๐Ÿ“

The JSON value a JSONPath query is applied to is, by definition, a valid JSON value. A JSON value is often constructed by parsing a JSON text.

The parsing of a JSON text into a JSON value, and what happens if a JSON text does not represent valid JSON, are not defined by this document. Sections 4 and 8 of [RFC8259] identify specific situations that may conform to the grammar for JSON texts but are not interoperable uses of JSON, as they may cause unpredictable behavior. This document does not attempt to define predictable behavior for JSONPath queries in these situations ๐Ÿ’€.

Specifically, the "Semantics" subsections of Sections 2.3.1, 2.3.2, 2.3.5, and 2.5.2 describe behavior that becomes unpredictable when the JSON value for one of the objects under consideration was constructed out of JSON text that has multiple members for a single object sharing the same member name ("duplicate names"; see Section 4 of [RFC8259]). Also, when selecting a child by name (Section 2.3.1) and comparing strings (Section 2.3.5.2.2), it is assumed these strings are sequences of Unicode scalar values. The behavior becomes unpredictable if they are not (Section 8.2 of [RFC8259]) ๐Ÿ™ˆ.

1.4. Overview of JSONPath Expressions ๐ŸŒˆ

A JSONPath expression is applied to a JSON value, known as the query argument. The output is a nodelist.

A JSONPath expression consists of an identifier followed by a series of zero or more segments, and each of those contains one or more selectors โญ.

1.4.1. Identifiers

The root node identifier $ refers to the root node of the query argument, i.e., to the argument as a whole.

The current node identifier @ refers to the current node in the context of the evaluation of a filter expression (Section 2.3.5).

1.4.2. Segments

Segments select children ([<selectors>]) or descendants (..[<selectors>]) of an input value.

Segments can use bracket notation, for example:

$['store']['book'][0]['title']

or the more compact dot notation, for example:

$.store.book[0].title

Bracket notation contains one or more (comma-separated) selectors of any kind. Selectors are detailed in the next section.

A JSONPath expression may use a combination of bracket and dot notations.

This document treats the bracket notations as canonical and defines the shorthand dot notation in terms of bracket notation. Examples and descriptions use shorthand where convenient ๐ŸŽ€.

1.4.3. Selectors

A name selector, e.g., 'name', selects a named child of an object.

An index selector, e.g., 3, selects an indexed child of an array.

In the expression [*], a wildcard * (Section 2.3.2) selects all children of a node, and in the expression ..[*], it selects all descendants of a node.

An array slice start:end:step (Section 2.3.4) selects a series of elements from an array. You give it a start position, an end position, and an optional step value that moves the position from the start to the end ๐Ÿฆ‹.

A filter expression ?<logical-expr> selects certain children of an object or array, as in:

$.store.book[?@.price < 10].title ๐Ÿ’…

1.4.4. Summary

Table 1 provides a brief overview of JSONPath syntax, bestie ๐Ÿ“‹.

   +==================+================================================+
   | Syntax Element   | Description                                    |
   +==================+================================================+
   | $                | root node identifier (Section 2.2)             |
   +------------------+------------------------------------------------+
   | @                | current node identifier (Section 2.3.5)        |
   |                  | (valid only within filter selectors)           |
   +------------------+------------------------------------------------+
   | [<selectors>]    | child segment (Section 2.5.1): selects         |
   |                  | zero or more children of a node                |
   +------------------+------------------------------------------------+
   | .name            | shorthand for ['name']                         |
   +------------------+------------------------------------------------+
   | .*               | shorthand for [*]                              |
   +------------------+------------------------------------------------+
   | ..[<selectors>]  | descendant segment (Section 2.5.2):            |
   |                  | selects zero or more descendants of a node     |
   +------------------+------------------------------------------------+
   | ..name           | shorthand for ..['name']                       |
   +------------------+------------------------------------------------+
   | ..*              | shorthand for ..[*]                            |
   +------------------+------------------------------------------------+
   | 'name'           | name selector (Section 2.3.1): selects a       |
   |                  | named child of an object                       |
   +------------------+------------------------------------------------+
   | *                | wildcard selector (Section 2.3.2): selects     |
   |                  | all children of a node                         |
   +------------------+------------------------------------------------+
   | 3                | index selector (Section 2.3.3): selects an     |
   |                  | indexed child of an array (from 0)             |
   +------------------+------------------------------------------------+
   | 0:100:5          | array slice selector (Section 2.3.4):          |
   |                  | start:end:step for arrays                      |
   +------------------+------------------------------------------------+
   | ?<logical-expr>  | filter selector (Section 2.3.5): selects       |
   |                  | particular children using a logical            |
   |                  | expression                                     |
   +------------------+------------------------------------------------+
   | length(@.foo)    | function extension (Section 2.4): invokes      |
   |                  | a function in a filter expression              |
   +------------------+------------------------------------------------+

                    Table 1: Overview of JSONPath Syntax

1.5. JSONPath Examples ๐Ÿง

This section is informative. It provides examples of JSONPath expressions ๐Ÿ’–.

The examples are based on the simple JSON value shown in Figure 1, representing a bookstore (which also has a bicycle) ๐Ÿ“š.

   { "store": {
       "book": [
         { "category": "reference",
           "author": "Nigel Rees",
           "title": "Sayings of the Century",
           "price": 8.95
         },
         { "category": "fiction",
           "author": "Evelyn Waugh",
           "title": "Sword of Honour",
           "price": 12.99
         },
         { "category": "fiction",
           "author": "Herman Melville",
           "title": "Moby Dick",
           "isbn": "0-553-21311-3",
           "price": 8.99
         },
         { "category": "fiction",
           "author": "J. R. R. Tolkien",
           "title": "The Lord of the Rings",
           "isbn": "0-395-19395-8",
           "price": 22.99
         }
       ],
       "bicycle": {
         "color": "red",
         "price": 399
       }
     }
   }

                        Figure 1: Example JSON Value

Table 2 shows some JSONPath queries that might be applied to this example and their intended results ๐ŸŒธ.

    +========================+=======================================+
    | JSONPath               | Intended Result                       |
    +========================+=======================================+
    | $.store.book[*].author | the authors of all books in the store |
    +------------------------+---------------------------------------+
    | $..author              | all authors                           |
    +------------------------+---------------------------------------+
    | $.store.*              | all things in the store, which are    |
    |                        | some books and a red bicycle          |
    +------------------------+---------------------------------------+
    | $.store..price         | the prices of everything in the store |
    +------------------------+---------------------------------------+
    | $..book[2]             | the third book                        |
    +------------------------+---------------------------------------+
    | $..book[2].author      | the third book's author               |
    +------------------------+---------------------------------------+
    | $..book[2].publisher   | empty result: the third book does not |
    |                        | have a "publisher" member             |
    +------------------------+---------------------------------------+
    | $..book[-1]            | the last book in order                |
    +------------------------+---------------------------------------+
    | $..book[0,1]           | the first two books                   |
    | $..book[:2]            |                                       |
    +------------------------+---------------------------------------+
    | $..book[?@.isbn]       | all books with an ISBN number         |
    +------------------------+---------------------------------------+
    | $..book[?@.price<10]   | all books cheaper than 10             |
    +------------------------+---------------------------------------+
    | $..*                   | all member values and array elements  |
    |                        | contained in the input value          |
    +------------------------+---------------------------------------+

         Table 2: Example JSONPath Expressions and Their Intended
              Results When Applied to the Example JSON Value

2. JSONPath Syntax and Semantics ๐Ÿ‘‘

2.1. Overview โœจ

okay so a JSONPath expression is a string that, when you apply it to a JSON value (the query argument), picks out zero or more nodes of that argument and hands them back as a nodelist ๐Ÿ’–

every query MUST be encoded in UTF-8 ๐Ÿซถ. the grammar in this doc assumes the UTF-8 form gets decoded into Unicode scalar values first, the way [RFC3629] describes it. other approaches are fine too, as long as they land on the same result ๐Ÿฆ‹

a string you want to use as a JSONPath query has to be well-formed and valid ๐ŸŽ€. it's well-formed if it matches the ABNF syntax in this doc. it's valid if it's well-formed AND it passes both of these semantic checks:

  1. integer numbers in the query that matter for the processing (like index values and steps) MUST be inside the exact integer range from I-JSON (see Section 2.2 of [RFC7493]), which is [-(2^53)+1, (2^53)-1] ๐Ÿ”ข
  2. uses of function extensions MUST be well-typed, as described in Section 2.4.3 ๐Ÿ’…

a JSONPath implementation MUST raise an error for any query that isn't well-formed and valid ๐Ÿšจ. well-formedness and validity don't depend on what JSON value the query gets run against, and no more well-formedness or validity errors can show up while the query is running. this neatly separates problems in the query from mismatches that might come from actual flaws in the data ๐Ÿงน

mismatches between the structure a valid query expects and the structure in the data can give empty results ๐Ÿซ . those can be unexpected and point to bugs in either one, so implementations might want to offer diagnostics that help the dev figure out why a result came back empty ๐Ÿ”

obviously an implementation can still fail while running a query, like if it runs out of resources, but this doc doesn't model that ๐Ÿ™ˆ. what it does say is that the implementation MUST NOT silently malfunction ๐Ÿ˜ญ. specifically, if a valid query runs against a structured value that's too big to process correctly (say it needs numbers outside the range of exact values), the implementation MUST provide an indication of overflow ๐Ÿšฉ

(if you know the HTTP error model, the well-formedness and validity stuff is basically giving 400 energy, and resource depletion and friends are like 500 errors ๐Ÿ’€)

2.1.1. Syntax

syntactically, a JSONPath query is a root identifier ($), which stands for a nodelist holding the root node of the query argument, followed by a possibly empty run of segments ๐ŸŒธ

jsonpath-query      = root-identifier segments
segments            = *(S segment)

B                   = %x20 /    ; Space
                      %x09 /    ; Horizontal tab
                      %x0A /    ; Line feed or New line
                      %x0D      ; Carriage return
S                   = *B        ; optional blank space

segments get their full syntax and semantics in Section 2.5 ๐Ÿงš

2.1.2. Semantics

in this doc, the semantics of a query describe the required results and don't tell implementations how to do their internal thing ๐Ÿง . the doc might describe stuff step by step, but those descriptions are only normative in the sense that any implementation MUST produce an identical result. implementers aren't required to use the same algorithms, so no stress ๐Ÿซถ

so the deal is: a valid query gets run against a value (the query argument) and makes a nodelist (basically a list of zero or more nodes of the value) โœจ

the query is a root identifier followed by zero or more segments. each segment gets applied to whatever the previous piece gave back, and then feeds its output into the next one. these results and inputs are all nodelists ๐ŸŽ€

the nodelist from the root identifier holds one node, which is the query argument ๐Ÿ‘‘. the nodelist from the last segment is what gets presented as the result of the query. depending on the API, that might be an array of the JSON values at those nodes, an array of Normalized Paths pointing at the nodes, or both, or whatever representation the implementation likes ๐Ÿ’–. note: an empty nodelist is a totally valid query result ๐Ÿซง

a segment runs on each node in its input nodelist, one at a time, and the resulting nodelists get stuck together in the order of the input nodes they came from. that's the segment's result ๐Ÿ”—. a node can get picked more than once, and it shows up that many times in the nodelist. duplicates are NOT removed ๐Ÿ™…โ€โ™€๏ธ. oops, sorry, correction: duplicate nodes are not removed, so they stay in there ๐Ÿ“

a syntactically valid segment MUST NOT produce errors when the query runs ๐Ÿ›ก๏ธ. that means stuff that might look like a mistake, like using an index outside an array's range, just selects fewer nodes instead of blowing up ๐Ÿคทโ€โ™€๏ธ. (there's more on this in the intro to Section 2.1 ๐Ÿฉท)

because of that approach, if any segment gives back an empty nodelist, the whole query gives back an empty nodelist ๐Ÿฅฒ

if the semantics give an implementation a choice between several possible orderings, a particular implementation may produce different orderings in successive runs of the same query ๐ŸŒˆ

2.1.3. Example

let's look at this one ๐Ÿ‘€. with the query argument {"a":[{"b":0},{"b":1},{"c":2}]}, the query $.a[*].b picks out this list of nodes (shown here by their values): 0, 1 ๐Ÿฆ‹

the query is $ followed by three segments: .a, [*], and .b ๐Ÿงฉ

first up, $ makes a nodelist with just the query argument in it ๐Ÿ‘‘

next, .a picks from any object input node and grabs the node for the member value whose name is "a". the result is again a single-node list: [{"b":0},{"b":1},{"c":2}] ๐Ÿ’…

then [*] grabs all the elements from the input array node. that's three nodes: {"b":0}, {"b":1}, and {"c":2} ๐Ÿ”ฅ

finally, .b picks from any object input node that has a member named b and grabs the member value for that name. the result is a list with 0, 1 in it. that's a concatenation of three lists: two of length one holding 0 and 1 respectively, plus one of length zero ๐ŸŽ‰

2.2. Root Identifier

2.2.1. Syntax

every JSONPath query MUST begin with the root identifier $ ๐ŸŒŸ. the one exception is queries inside filter expressions (see Section 2.3.5), which don't have to start that way ๐Ÿซฃ

root-identifier     = "$"

2.2.2. Semantics

the root identifier $ stands for the root node of the query argument, and it makes a nodelist that holds just that root node ๐Ÿ‘‘

2.2.3. Examples

Heads up ๐ŸŽ€: in this example and the ones that follow in Sections 2.2 and 2.3 (except for Table 11), we'll show a JSON text for the JSON value used as the query argument, then a table with these columns:
  • Query: an example query to run on the query argument ๐Ÿ”Ž
  • Result: the query result as a list of JSON values found in the query argument ๐Ÿ’–
  • Result Path: the query result as a list of (normalized) paths into the query argument, showing where those JSON values live ๐Ÿ—บ๏ธ
  • Comment: some descriptive info ๐Ÿ’ฌ

JSON:

{"k": "v"}

Queries:

         +=======+============+=============+===========+
         | Query | Result     | Result Path | Comment   |
         +=======+============+=============+===========+
         |   $   | {"k": "v"} |      $      | Root node |
         +-------+------------+-------------+-----------+

Table 3: Root Identifier Example ๐ŸŒธ

2.3. Selectors

selectors only show up inside child segments (Section 2.5.1) and descendant segments (Section 2.5.2) ๐Ÿงš

a selector makes a nodelist with zero or more children of the input value ๐ŸŒฑ

there are different kinds of selectors: some produce children of objects, some produce children of arrays, and some produce children of either objects or arrays ๐Ÿ’œ

selector            = name-selector /
                      wildcard-selector /
                      slice-selector /
                      index-selector /
                      filter-selector

the syntax and semantics of each kind of selector are defined below โœจ

2.3.1. Name Selector

2.3.1.1. Syntax

Okay so a name selector '<name>' picks out at most one object member value ๐ŸŽ€. Unlike JSON, JSONPath lets you wrap strings in single or double quotes, which is such a vibe ๐Ÿ’….

name-selector       = string-literal

string-literal      = %x22 *double-quoted %x22 /     ; "string"
                      %x27 *single-quoted %x27       ; 'string'

double-quoted       = unescaped /
                      %x27      /                    ; '
                      ESC %x22  /                    ; \"
                      ESC escapable

single-quoted       = unescaped /
                      %x22      /                    ; "
                      ESC %x27  /                    ; \'
                      ESC escapable

ESC                 = %x5C                           ; \ backslash

unescaped           = %x20-21 /                      ; see RFC 8259
                         ; omit 0x22 "
                      %x23-26 /
                         ; omit 0x27 '
                      %x28-5B /
                         ; omit 0x5C \
                      %x5D-D7FF /
                         ; skip surrogate code points
                      %xE000-10FFFF

escapable           = %x62 / ; b BS backspace U+0008
                      %x66 / ; f FF form feed U+000C
                      %x6E / ; n LF line feed U+000A
                      %x72 / ; r CR carriage return U+000D
                      %x74 / ; t HT horizontal tab U+0009
                      "/"  / ; / slash (solidus) U+002F
                      "\"  / ; \ backslash (reverse solidus) U+005C
                      (%x75 hexchar) ;  uXXXX U+XXXX

hexchar             = non-surrogate /
                      (high-surrogate "\" %x75 low-surrogate)
non-surrogate       = ((DIGIT / "A"/"B"/"C" / "E"/"F") 3HEXDIG) /
                      ("D" %x30-37 2HEXDIG )
high-surrogate      = "D" ("8"/"9"/"A"/"B") 2HEXDIG
low-surrogate       = "D" ("C"/"D"/"E"/"F") 2HEXDIG

HEXDIG              = DIGIT / "A" / "B" / "C" / "D" / "E" / "F"

Notes:

  • Double-quoted strings follow the JSON string syntax (Section 7 of [RFC8259]), and single-quoted strings follow a similar pattern ๐ŸŒธ. Nobody tried to make this syntax fancier ๐Ÿ™ˆ. So if you want to escape a character with a scalar value above 0xFFFF, like U+1F041 ("๐Ÿ", DOMINO TILE HORIZONTAL-02-02), you need a pair of surrogate escapes instead, which is "\uD83C\uDC41" in this case ๐Ÿงš.
  • Alphabetic characters in quoted strings are case-insensitive in ABNF ๐Ÿฆ‹, so each hexadecimal digit inside \u escapes (as specified in the rules referenced by hexchar) can be lowercase or uppercase. The u in \u has to be lowercase, indicated as %x75.

2.3.1.2. Semantics

So a name-selector string MUST be converted into a member name M ๐Ÿ’… by peeling off the surrounding quotes and swapping each escape sequence for its matching Unicode character, as shown in Table 4:

+=================+===================+=============================+
| Escape Sequence | Unicode Character | Description                 |
+=================+===================+=============================+
|        \b       |       U+0008      | BS backspace                |
+-----------------+-------------------+-----------------------------+
|        \t       |       U+0009      | HT horizontal tab           |
+-----------------+-------------------+-----------------------------+
|        \n       |       U+000A      | LF line feed                |
+-----------------+-------------------+-----------------------------+
|        \f       |       U+000C      | FF form feed                |
+-----------------+-------------------+-----------------------------+
|        \r       |       U+000D      | CR carriage return          |
+-----------------+-------------------+-----------------------------+
|        \"       |       U+0022      | quotation mark              |
+-----------------+-------------------+-----------------------------+
|        \'       |       U+0027      | apostrophe                  |
+-----------------+-------------------+-----------------------------+
|        \/       |       U+002F      | slash (solidus)             |
+-----------------+-------------------+-----------------------------+
|        \\       |       U+005C      | backslash (reverse          |
|                 |                   | solidus)                    |
+-----------------+-------------------+-----------------------------+
|      \uXXXX     |        see        | hexadecimal escape          |
|                 |  Section 2.3.1.1  |                             |
+-----------------+-------------------+-----------------------------+

                 Table 4: Escape Sequence Replacements

Applying the name-selector to an object node selects a member value whose name equals the member name M, or selects nothing if there is no such member value ๐Ÿฆ‹. Nothing is selected from a value that is not an object.

Note: Processing the name selector means comparing the member name string M with the member name strings in the JSON that the selector is being applied to ๐Ÿ‘€. Two strings MUST be considered equal if and only if they are identical sequences of Unicode scalar values. In other words, normalization operations MUST NOT be applied to either the member name string M from the JSONPath or the member name strings in the JSON prior to comparison. Bestie, this part is serious ๐Ÿ’€.

2.3.1.3. Examples

JSON:

{
  "o": {"j j": {"k.k": 3}},
  "'": {"@": 2}
}

Queries:

The examples in Table 5 show the name selector in use by child segments ๐Ÿ“.

 +====================+=======+=======================+============+
 |       Query        |Result |      Result Paths     | Comment    |
 +====================+=======+=======================+============+
 |     $.o['j j']     |{"k.k":|     $['o']['j j']     | Named      |
 |                    |3}     |                       | value in   |
 |                    |       |                       | a nested   |
 |                    |       |                       | object     |
 +--------------------+-------+-----------------------+------------+
 | $.o['j j']['k.k']  |3      |  $['o']['j j']['k.k'] | Nesting    |
 |                    |       |                       | further    |
 |                    |       |                       | down       |
 +--------------------+-------+-----------------------+------------+
 | $.o["j j"]["k.k"]  |3      |  $['o']['j j']['k.k'] | Different  |
 |                    |       |                       | delimiter  |
 |                    |       |                       | in the     |
 |                    |       |                       | query,     |
 |                    |       |                       | unchanged  |
 |                    |       |                       | Normalized |
 |                    |       |                       | Path       |
 +--------------------+-------+-----------------------+------------+
 |    $["'"]["@"]     |2      |      $['\'']['@']     | Unusual    |
 |                    |       |                       | member     |
 |                    |       |                       | names      |
 +--------------------+-------+-----------------------+------------+

                   Table 5: Name Selector Examples

2.3.2. Wildcard Selector

2.3.2.1. Syntax

The wildcard selector is just an asterisk โญ.

wildcard-selector   = "*"

2.3.2.2. Semantics

A wildcard selector selects the nodes of all children of an object or array ๐ŸŒˆ. The order in which an object's children show up in the resultant nodelist is not stipulated, since JSON objects are unordered ๐Ÿ™ƒ. Children of an array appear in array order in the resultant nodelist.

Note that the children of an object are its member values, not its member names ๐Ÿ’….

The wildcard selector selects nothing from a primitive JSON value (that is, a number, a string, true, false, or null) ๐Ÿซถ.

2.3.2.3. Examples

JSON:

{
  "o": {"j": 1, "k": 2},
  "a": [5, 3]
}

Queries:

The examples in Table 6 show the wildcard selector in use by a child segment โœจ.

      +========+==========+=============+===================+
      | Query  | Result   |    Result   | Comment           |
      |        |          |    Paths    |                   |
      +========+==========+=============+===================+
      |  $[*]  | {"j": 1, |    $['o']   | Object values     |
      |        | "k": 2}  |    $['a']   |                   |
      |        | [5, 3]   |             |                   |
      +--------+----------+-------------+-------------------+
      | $.o[*] | 1        | $['o']['j'] | Object values     |
      |        | 2        | $['o']['k'] |                   |
      +--------+----------+-------------+-------------------+
      | $.o[*] | 2        | $['o']['k'] | Alternative       |
      |        | 1        | $['o']['j'] | result            |
      +--------+----------+-------------+-------------------+
      | $.o[*, | 1        | $['o']['j'] | Non-deterministic |
      |   *]   | 2        | $['o']['k'] | ordering          |
      |        | 2        | $['o']['k'] |                   |
      |        | 1        | $['o']['j'] |                   |
      +--------+----------+-------------+-------------------+
      | $.a[*] | 5        |  $['a'][0]  | Array members     |
      |        | 3        |  $['a'][1]  |                   |
      +--------+----------+-------------+-------------------+

                Table 6: Wildcard Selector Examples

The example above with the query $.o[*, *] shows that the wildcard selector may produce nodelists in distinct orders each time it appears in the child segment, when it is applied to an object node with two or more members. It doesn't do this when it's applied to object nodes with fewer than two members or to array nodes ๐Ÿฆ‹.

2.3.3. Index Selector

2.3.3.1. Syntax

An index selector <index> matches at most one array element value ๐Ÿ“.

index-selector      = int                        ; decimal integer

int                 = "0" /
                      (["-"] DIGIT1 *DIGIT)      ; - optional
DIGIT1              = %x31-39                    ; 1-9 non-zero digit

Applying the numerical index-selector selects the corresponding element ๐ŸŽฏ. JSONPath lets it be negative too (see Section 2.3.3.2).

To be valid, the index selector value MUST be in the I-JSON range of exact values (see Section 2.1) ๐Ÿ’–.

Notes:

  • An index-selector is an integer (in base 10, as in JSON numbers) ๐Ÿ”ข.
  • As in JSON numbers, the syntax does not allow octal-like integers with leading zeros, such as 01 or -01 ๐Ÿ™…โ€โ™€๏ธ.

2.3.3.2. Semantics

A non-negative index-selector applied to an array selects an array element using a zero-based index ๐Ÿงฎ. For example, the selector 0 selects the first element, and the selector 4 selects the fifth element of a sufficiently long array. Nothing is selected, and it is not an error, if the index lies outside the range of the array. Nothing is selected from a value that is not an array ๐ŸŒธ.

A negative index-selector counts from the array's end backwards ๐Ÿ”™. It gets an equivalent non-negative index-selector by adding the length of the array to the negative index. For example, the selector -1 selects the last element, and the selector -2 selects the penultimate element of an array with at least two elements. As with non-negative indexes, it is not an error if such an element does not exist. This simply means that no element is selected ๐Ÿซ .

2.3.3.3. Examples

okay so, JSON first ๐Ÿ’…

["a","b"]

Queries time ๐Ÿฆ‹ Table 7 shows the index selector doing its thing inside a child segment, and it's giving clean array vibes ๐ŸŒธ

    +=======+========+==============+================================+
    | Query | Result | Result Paths | Comment                        |
    +=======+========+==============+================================+
    |  $[1] | "b"    |     $[1]     | Element of array               |
    +-------+--------+--------------+--------------------------------+
    | $[-2] | "a"    |     $[0]     | Element of array, from the end |
    +-------+--------+--------------+--------------------------------+

                     Table 7: Index Selector Examples

2.3.4. Array Slice Selector

2.3.4.1. Syntax

okay this one is so fun ๐ŸŽ€ The array slice selector looks like <start>:<end>:<step>. It picks elements from an array starting at index <start>, and it goes up to but does not include <end>, moving forward by step (the default is 1) โœจ

slice-selector      = [start S] ":" S [end S] [":" [S step ]]

start               = int       ; included in selection
end                 = int       ; not included in selection
step                = int       ; default: 1

Basically you get three optional decimal integers separated by colons ๐Ÿ’– The second colon can get dropped when the third integer isn't there ๐Ÿ™ˆ

To be valid, the integers provided MUST be in the I-JSON range of exact values (see Section 2.1). no skipping this bestie, it's a must ๐Ÿ”ฅ

2.3.4.2. Semantics

The slice selector was inspired by the slice operator that was proposed for ECMAScript 4 (ES4), which never actually got released, and also by the one in Python ๐Ÿงš

2.3.4.2.1. Informal Introduction

This section is informative.

Slicing is inspired by Array.prototype.slice from JavaScript, as defined by the ECMA-262 standard [ECMA-262], plus an extra step parameter that comes from Python's slice expression ๐Ÿ’œ

So the expression start:end:step picks elements at indices starting at start, moving by step, and stopping before end (which is itself left out) ๐ŸŒˆ For example, 1:3 (step defaults to 1) gives you indices 1 and 2 in that order, while 1:5:2 gives you indices 1 and 3 โœจ

When step is negative, the elements come back in reverse order. So 5:1:-2 picks indices 5 and 3 (in that order), and ::-1 grabs every single element in reverse ๐Ÿ˜ญ

When step is 0, nothing gets selected. (This is the one spot where it differs from Python, which throws an error instead ๐Ÿ’€)

The next section spells everything out fully, so it doesn't need JavaScript or Python to explain it ๐Ÿซถ

2.3.4.2.2. Normative Semantics

A slice expression selects a subset of the input array's elements, in either the same order as the array or the reverse order, depending on the sign of step. If the node isn't an array, it selects no nodes at all ๐Ÿ™…โ€โ™€๏ธ

A slice is defined by two slice parameters, start and end, plus an iteration delta, step. All of them are optional. In the rest of this section, len is the length of the input array ๐Ÿ“

The default value for step is 1. The defaults for start and end depend on the sign of step, as shown in Table 8 ๐Ÿ‘‘

                 +===========+=========+==========+
                 | Condition | start   | end      |
                 +===========+=========+==========+
                 | step >= 0 | 0       | len      |
                 +-----------+---------+----------+
                 | step < 0  | len - 1 | -len - 1 |
                 +-----------+---------+----------+

                    Table 8: Default Array Slice
                        start and end Values

start and end can't be used as slice bounds straight away. They have to be normalized first. Normalization is defined like this:

FUNCTION Normalize(i, len):
  IF i >= 0 THEN
    RETURN i
  ELSE
    RETURN len + i
  END IF

Applying the array index expression i to an array of length len gives the same result as the array slicing expression Normalize(i, len):Normalize(i, len)+1:1 ๐Ÿฆ‹

start and end get used to work out the slice bounds lower and upper. The direction of iteration, set by the sign of step, decides which one is the lower bound and which one is the upper bound ๐Ÿ’…

FUNCTION Bounds(start, end, step, len):
  n_start = Normalize(start, len)
  n_end = Normalize(end, len)

  IF step >= 0 THEN
    lower = MIN(MAX(n_start, 0), len)
    upper = MIN(MAX(n_end, 0), len)
  ELSE
    upper = MIN(MAX(n_start, -1), len-1)
    lower = MIN(MAX(n_end, -1), len-1)
  END IF

  RETURN (lower, upper)

The slice expression selects elements whose indices sit between the lower and upper bounds. In the pseudocode below, a(i) is the (i+1)th element of the array a. So a(0) is the first element, a(1) is the second, and so on ๐ŸŒธ

IF step > 0 THEN

  i = lower
  WHILE i < upper:
    SELECT a(i)
    i = i + step
  END WHILE

ELSE if step < 0 THEN

  i = upper
  WHILE lower < i:
    SELECT a(i)
    i = i + step
  END WHILE

END IF

When step = 0, no elements get selected, so the result array is empty ๐Ÿฅบ

2.3.4.3. Examples

JSON:

["a", "b", "c", "d", "e", "f", "g"]

Queries:

The examples in Table 9 show the array slice selector in use by a child segment ๐Ÿงš

            +===========+========+========+==========+
            |   Query   | Result | Result | Comment  |
            |           |        | Paths  |          |
            +===========+========+========+==========+
            |   $[1:3]  | "b"    |  $[1]  | Slice    |
            |           | "c"    |  $[2]  | with     |
            |           |        |        | default  |
            |           |        |        | step     |
            +-----------+--------+--------+----------+
            |   $[5:]   | "f"    |  $[5]  | Slice    |
            |           | "g"    |  $[6]  | with no  |
            |           |        |        | end      |
            |           |        |        | index    |
            +-----------+--------+--------+----------+
            |  $[1:5:2] | "b"    |  $[1]  | Slice    |
            |           | "d"    |  $[3]  | with     |
            |           |        |        | step 2   |
            +-----------+--------+--------+----------+
            | $[5:1:-2] | "f"    |  $[5]  | Slice    |
            |           | "d"    |  $[3]  | with     |
            |           |        |        | negative |
            |           |        |        | step     |
            +-----------+--------+--------+----------+
            |  $[::-1]  | "g"    |  $[6]  | Slice in |
            |           | "f"    |  $[5]  | reverse  |
            |           | "e"    |  $[4]  | order    |
            |           | "d"    |  $[3]  |          |
            |           | "c"    |  $[2]  |          |
            |           | "b"    |  $[1]  |          |
            |           | "a"    |  $[0]  |          |
            +-----------+--------+--------+----------+

              Table 9: Array Slice Selector Examples

2.3.5. Filter Selector

okay filter selectors are the ones that loop through the elements or members of structured values, aka JSON arrays and objects ๐Ÿ” The structured values are identified in the nodelist that the child or descendant segment hands over to the filter selector ๐Ÿ’–

For each iteration (each element or member), a logical expression (the filter expression) gets evaluated, and that decides whether the node for that element or member gets selected โœจ (Even though a logical expression technically evaluates to what math calls a Boolean value, this spec says logical so it doesn't get mixed up with the Boolean values JSON can represent ๐Ÿค“)

During the iteration, the filter expression receives the node of each array element or object member value of the structured value being filtered. That element or member value is called the current node ๐Ÿฆ‹

The current node can be the start of one or more JSONPath queries inside subexpressions of the filter expression. You write it as the current-node-identifier @. Each JSONPath query can be used to check whether it has any result at all, to grab a specific JSON value from the result so it can be compared, or as a function argument ๐ŸŒˆ

Filter selectors can use function extensions, which Section 2.4 covers. Inside a filter selector's logical expression, function expressions can operate on nodelists and values ๐Ÿ’… The set of functions is extensible. A number of them are predefined (see Section 2.4), and you can register more through the "Function Extensions" subregistry (Section 3.2) ๐Ÿ‘‘ When a function is defined, it gets a unique name, and its return value and each of its parameters each get a declared type. The type system is pretty limited. Its job is to express restrictions that, without functions, are already implied by the grammar of filter expressions. It also guides the conversions in Section 2.4.2, which copy the way different kinds of expressions get handled by the grammar when no function expressions are involved ๐ŸŽ€

2.3.5.1. Syntax

Okay bestie, the filter selector is the star of the show โœจ It looks like this:

filter-selector     = "?" S logical-expr

Here's the tea ๐Ÿซ– The filter expression is built only from pieces that have no side effects, so the order in which things get evaluated doesn't need to be defined (and it isn't). Same goes for && and || (we'll get to those). A short-circuiting version and a fully evaluating version both land on the same answer, so either way of doing it is totally valid ๐Ÿ’…

You can reach the current node with the current node identifier @. It points at the node of the filter-selector that's directly wrapping it ๐ŸŽ€ Note: inside nested filter-selectors there's no way to reach the current node of any other filter-selector, only the one that's directly enclosing the identifier. (Only the immediate parent, not the grandparents ๐Ÿ™ˆ)

Logical expressions have the usual Boolean operators: || for OR, && for AND, and ! for NOT. They behave like normal Boolean algebra and follow its laws (check out [BOOLEAN-LAWS] if you want the receipts ๐Ÿง ). Parentheses MAY be used within logical-expr for grouping ๐ŸŒธ

You don't have to wrap a logical-expr in parentheses, which was required back in [JSONPath-orig]. You can, though, and the meaning is exactly the same either way ๐Ÿฆ‹

logical-expr        = logical-or-expr
logical-or-expr     = logical-and-expr *(S "||" S logical-and-expr)
                        ; disjunction
                        ; binds less tightly than conjunction
logical-and-expr    = basic-expr *(S "&&" S basic-expr)
                        ; conjunction
                        ; binds more tightly than disjunction

basic-expr          = paren-expr /
                      comparison-expr /
                      test-expr

paren-expr          = [logical-not-op S] "(" S logical-expr S ")"
                                        ; parenthesized expression
logical-not-op      = "!"               ; logical NOT operator

A test expression is basically an existence check ๐Ÿ‘€ It either checks whether a node exists using an embedded query (see Section 2.3.5.2.1), or it checks the result of a function expression (see Section 2.4). For functions, if the declared result type is LogicalType (see Section 2.4.1), the test asks whether the result is LogicalTrue. If the declared result type is NodesType, it asks whether the result is non-empty. If the declared result type is ValueType, using it in a test expression is not well-typed (see Section 2.4.3). Not valid, sis ๐Ÿ’€

test-expr           = [logical-not-op S]
                      (filter-query / ; existence/non-existence
                       function-expr) ; LogicalType or NodesType
filter-query        = rel-query / jsonpath-query
rel-query           = current-node-identifier segments
current-node-identifier = "@"

Comparison expressions are for comparing primitive values, which means numbers, strings, true, false, and null ๐Ÿ’– You can get those values from a literal, from a singular query (a query that selects at most one node, whose value then gets used), or from a function expression of type ValueType (see Section 2.4).

comparison-expr     = comparable S comparison-op S comparable
literal             = number / string-literal /
                      true / false / null
comparable          = literal /
                      singular-query / ; singular query value
                      function-expr    ; ValueType
comparison-op       = "==" / "!=" /
                      "<=" / ">=" /
                      "<"  / ">"

singular-query      = rel-singular-query / abs-singular-query
rel-singular-query  = current-node-identifier singular-query-segments
abs-singular-query  = root-identifier singular-query-segments
singular-query-segments = *(S (name-segment / index-segment))
name-segment        = ("[" name-selector "]") /
                      ("." member-name-shorthand)
index-segment       = "[" index-selector "]"

Literals look the way they do in JSON, with one extra perk: strings are allowed to use single-quote delimiters too ๐ŸŽ€

Note: ABNF treats alphabetic characters in quoted strings as case-insensitive, so inside a floating point number, the ABNF expression "e" can be either e or E โœจ

true, false, and null are lowercase only, and they're case-sensitive ๐Ÿ™…โ€โ™€๏ธ

number              = (int / "-0") [ frac ] [ exp ] ; decimal number
frac                = "." 1*DIGIT                  ; decimal fraction
exp                 = "e" [ "-" / "+" ] 1*DIGIT    ; decimal exponent
true                = %x74.72.75.65                ; true
false               = %x66.61.6c.73.65             ; false
null                = %x6e.75.6c.6c                ; null

Here's the operator pecking order ๐Ÿ‘‘ Table 10 lists the filter expression operators from highest precedence (binds most tightly) down to lowest (binds least tightly):

         +============+======================+=============+
         | Precedence |    Operator type     |    Syntax   |
         +============+======================+=============+
         |     5      |       Grouping       |    (...)    |
         |            | Function Expressions | _name_(...) |
         +------------+----------------------+-------------+
         |     4      |     Logical NOT      |      !      |
         +------------+----------------------+-------------+
         |     3      |      Relations       |    == !=    |
         |            |                      |  < <= > >=  |
         +------------+----------------------+-------------+
         |     2      |     Logical AND      |      &&     |
         +------------+----------------------+-------------+
         |     1      |      Logical OR      |      ||     |
         +------------+----------------------+-------------+

           Table 10: Filter Expression Operator Precedence

2.3.5.2. Semantics

The filter selector only works with arrays and objects ๐Ÿงบ Its result is a list of (_zero_, _one_, _multiple_, or _all_) of their array elements or member values, respectively. Point it at a primitive value and it selects nothing, so it doesn't add anything to the filter selector's result ๐Ÿ™ˆ

In the resulting nodelist, the children of an array come out ordered by their position in the array ๐Ÿ“‹ Children of an object (not an array) can show up in any order, because JSON objects are unordered, and the spec doesn't pin that order down ๐Ÿงš

2.3.5.2.1. Existence Tests

A query on its own in a logical context is an existence test. It gives true if the query selects at least one node, and false if it selects none ๐Ÿฆ‹

Existence tests are different from comparisons in two ways:

  • They work with any relative or absolute query, not only singular ones.
  • They work with queries that select structured values.

If you want to check the value of a node a query selected, you need an explicit comparison. For example, to check whether the node selected by @.foo is null, write @.foo == null (see Section 2.6). Don't use the negated existence test !@.foo for that, because it gives false whenever @.foo selects a node, no matter what the node's value is ๐Ÿ™ƒ In the same way, @.foo == false is true only when @.foo selects a node and that node's value is false ๐Ÿ’…

2.3.5.2.2. Comparisons

The comparison operators == and < get defined first, and then they're used to define !=, <=, >, and >= ๐Ÿงฎ

When either side of a comparison ends up with an empty nodelist or the special result Nothing (see Section 2.4.1):

  • A comparison using the operator == yields true if and only if the other side also results in an empty nodelist or the special result Nothing.
  • A comparison using the operator < yields false.

When any query or function expression on either side of a comparison results in a nodelist with exactly one node, that side gets swapped out for the value of its node. Then:

  • A comparison using the operator == yields true if and only if the comparison is between:
    • numbers expected to interoperate, as per Section 2.2 of I-JSON [RFC7493], that compare equal using normal mathematical equality,
    • numbers, at least one of which is not expected to interoperate as per I-JSON, where the numbers compare equal using an implementation-specific equality,
    • equal primitive values that are not numbers,
    • equal arrays, that is, arrays of the same length where each element of the first array is equal to the corresponding element of the second array, or
    • equal objects with no duplicate names, that is, where:
      • both objects have the same collection of names (with no duplicates) and
      • for each of those names, the values associated with the name by the objects are equal.
  • A comparison using the operator < yields true if and only if the comparison is between values that are both numbers or both strings and that satisfy the comparison:
    • numbers expected to interoperate, as per Section 2.2 of I-JSON [RFC7493], MUST compare using the normal mathematical ordering; numbers not expected to interoperate, as per I-JSON, MAY compare using an implementation-specific ordering,
    • the empty string compares less than any non-empty string, and
    • a non-empty string compares less than another non-empty string if and only if the first string starts with a lower Unicode scalar value than the second string or if both strings start with the same Unicode scalar value and the remainder of the first string compares less than the remainder of the second string.

!=, <=, >, and >= are defined in terms of the other comparison operators ๐ŸŒˆ For any a and b:

  • The comparison a != b yields true if and only if a == b yields false.
  • The comparison a <= b yields true if and only if a < b yields true or a == b yields true.
  • The comparison a > b yields true if and only if b < a yields true.
  • The comparison a >= b yields true if and only if b < a yields true or a == b yields true.

2.3.5.3. Examples

Time for the examples ๐Ÿ“ The first set shows some comparison expressions and what they give when a particular JSON value goes in as input.

JSON:

{
  "obj": {"x": "y"},
  "arr": [2, 3]
}

Comparisons:

Okay bestie, the comparison table is still going, so here's the rest of it ๐Ÿ’…โœจ

       +========================+========+========================+
       |       Comparison       | Result |        Comment         |
       +========================+========+========================+
       | $.absent1 == $.absent2 |  true  |    Empty nodelists     |
       +------------------------+--------+------------------------+
       | $.absent1 <= $.absent2 |  true  |     == implies <=      |
       +------------------------+--------+------------------------+
       |    $.absent == 'g'     | false  |     Empty nodelist     |
       +------------------------+--------+------------------------+
       | $.absent1 != $.absent2 | false  |    Empty nodelists     |
       +------------------------+--------+------------------------+
       |    $.absent != 'g'     |  true  |     Empty nodelist     |
       +------------------------+--------+------------------------+
       |         1 <= 2         |  true  |   Numeric comparison   |
       +------------------------+--------+------------------------+
       |         1 > 2          | false  |   Numeric comparison   |
       +------------------------+--------+------------------------+
       |       13 == '13'       | false  |     Type mismatch      |
       +------------------------+--------+------------------------+
       |       'a' <= 'b'       |  true  |   String comparison    |
       +------------------------+--------+------------------------+
       |       'a' > 'b'        | false  |   String comparison    |
       +------------------------+--------+------------------------+
       |     $.obj == $.arr     | false  |     Type mismatch      |
       +------------------------+--------+------------------------+
       |     $.obj != $.arr     |  true  |     Type mismatch      |
       +------------------------+--------+------------------------+
       |     $.obj == $.obj     |  true  |   Object comparison    |
       +------------------------+--------+------------------------+
       |     $.obj != $.obj     | false  |   Object comparison    |
       +------------------------+--------+------------------------+
       |     $.arr == $.arr     |  true  |    Array comparison    |
       +------------------------+--------+------------------------+
       |     $.arr != $.arr     | false  |    Array comparison    |
       +------------------------+--------+------------------------+
       |      $.obj == 17       | false  |     Type mismatch      |
       +------------------------+--------+------------------------+
       |      $.obj != 17       |  true  |     Type mismatch      |
       +------------------------+--------+------------------------+
       |     $.obj <= $.arr     | false  | Objects and arrays do  |
       |                        |        | not offer < comparison |
       +------------------------+--------+------------------------+
       |     $.obj < $.arr      | false  | Objects and arrays do  |
       |                        |        | not offer < comparison |
       +------------------------+--------+------------------------+
       |     $.obj <= $.obj     |  true  |     == implies <=      |
       +------------------------+--------+------------------------+
       |     $.arr <= $.arr     |  true  |     == implies <=      |
       +------------------------+--------+------------------------+
       |       1 <= $.arr       | false  | Arrays do not offer <  |
       |                        |        |       comparison       |
       +------------------------+--------+------------------------+
       |       1 >= $.arr       | false  | Arrays do not offer <  |
       |                        |        |       comparison       |
       +------------------------+--------+------------------------+
       |       1 > $.arr        | false  | Arrays do not offer <  |
       |                        |        |       comparison       |
       +------------------------+--------+------------------------+
       |       1 < $.arr        | false  | Arrays do not offer <  |
       |                        |        |       comparison       |
       +------------------------+--------+------------------------+
       |      true <= true      |  true  |     == implies <=      |
       +------------------------+--------+------------------------+
       |      true > true       | false  | Booleans do not offer  |
       |                        |        |      < comparison      |
       +------------------------+--------+------------------------+

Table 11: Comparison Examples

Next up is the second batch, where we get to see full JSONPath queries using filter selectors and what they spit out when run on a JSON value. ๐Ÿฆ‹ Two of these queries use function extensions, so if you want the deets on those, check out Sections 2.4.6 and 2.4.7 ๐Ÿซถ

JSON:

{
  "a": [3, 5, 1, 2, 4, 6,
        {"b": "j"},
        {"b": "k"},
        {"b": {}},
        {"b": "kilo"}
       ],
  "o": {"p": 1, "q": 2, "r": 3, "s": 5, "t": {"u": 6}},
  "e": "f"
}

Queries:

Table 12 is where we see the filter selector working in a child segment ๐ŸŒธ

+==================+==============+=============+===================+
|      Query       | Result       |    Result   | Comment           |
|                  |              |    Paths    |                   |
+==================+==============+=============+===================+
|   $.a[?@.b ==    | {"b":        |  $['a'][9]  | Member value      |
|     'kilo']      | "kilo"}      |             | comparison        |
+------------------+--------------+-------------+-------------------+
|   $.a[?(@.b ==   | {"b":        |  $['a'][9]  | Equivalent query  |
|     'kilo')]     | "kilo"}      |             | with enclosing    |
|                  |              |             | parentheses       |
+------------------+--------------+-------------+-------------------+
|   $.a[?@>3.5]    | 5            |  $['a'][1]  | Array value       |
|                  | 4            |  $['a'][4]  | comparison        |
|                  | 6            |  $['a'][5]  |                   |
+------------------+--------------+-------------+-------------------+
|    $.a[?@.b]     | {"b": "j"}   |  $['a'][6]  | Array value       |
|                  | {"b": "k"}   |  $['a'][7]  | existence         |
|                  | {"b": {}}    |  $['a'][8]  |                   |
|                  | {"b":        |  $['a'][9]  |                   |
|                  | "kilo"}      |             |                   |
+------------------+--------------+-------------+-------------------+
|     $[?@.*]      | [3, 5, 1,    |    $['a']   | Existence of non- |
|                  | 2, 4, 6,     |    $['o']   | singular queries  |
|                  | {"b": "j"},  |             |                   |
|                  | {"b": "k"},  |             |                   |
|                  | {"b": {}},   |             |                   |
|                  | {"b":        |             |                   |
|                  | "kilo"}]     |             |                   |
|                  | {"p": 1,     |             |                   |
|                  | "q": 2,      |             |                   |
|                  | "r": 3,      |             |                   |
|                  | "s": 5,      |             |                   |
|                  | "t": {"u":   |             |                   |
|                  | 6}}          |             |                   |
+------------------+--------------+-------------+-------------------+
|   $[?@[?@.b]]    | [3, 5, 1,    |    $['a']   | Nested filters    |
|                  | 2, 4, 6,     |             |                   |
|                  | {"b": "j"},  |             |                   |
|                  | {"b": "k"},  |             |                   |
|                  | {"b": {}},   |             |                   |
|                  | {"b":        |             |                   |
|                  | "kilo"}]     |             |                   |
+------------------+--------------+-------------+-------------------+
| $.o[?@<3, ?@<3]  | 1            | $['o']['p'] | Non-deterministic |
|                  | 2            | $['o']['q'] | ordering          |
|                  | 2            | $['o']['q'] |                   |
|                  | 1            | $['o']['p'] |                   |
+------------------+--------------+-------------+-------------------+
| $.a[?@<2 || @.b  | 1            |  $['a'][2]  | Array value       |
|     == "k"]      | {"b": "k"}   |  $['a'][7]  | logical OR        |
+------------------+--------------+-------------+-------------------+
| $.a[?match(@.b,  | {"b": "j"}   |  $['a'][6]  | Array value       |
|     "[jk]")]     | {"b": "k"}   |  $['a'][7]  | regular           |
|                  |              |             | expression match  |
+------------------+--------------+-------------+-------------------+
| $.a[?search(@.b, | {"b": "j"}   |  $['a'][6]  | Array value       |
|     "[jk]")]     | {"b": "k"}   |  $['a'][7]  | regular           |
|                  | {"b":        |  $['a'][9]  | expression search |
|                  | "kilo"}      |             |                   |
+------------------+--------------+-------------+-------------------+
| $.o[?@>1 && @<4] | 2            | $['o']['q'] | Object value      |
|                  | 3            | $['o']['r'] | logical AND       |
+------------------+--------------+-------------+-------------------+
| $.o[?@>1 && @<4] | 3            | $['o']['r'] | Alternative       |
|                  | 2            | $['o']['q'] | result            |
+------------------+--------------+-------------+-------------------+
| $.o[?@.u || @.x] | {"u": 6}     | $['o']['t'] | Object value      |
|                  |              |             | logical OR        |
+------------------+--------------+-------------+-------------------+
| $.a[?@.b == $.x] | 3            |  $['a'][0]  | Comparison of     |
|                  | 5            |  $['a'][1]  | queries with no   |
|                  | 1            |  $['a'][2]  | values            |
|                  | 2            |  $['a'][3]  |                   |
|                  | 4            |  $['a'][4]  |                   |
|                  | 6            |  $['a'][5]  |                   |
+------------------+--------------+-------------+-------------------+
|   $.a[?@ == @]   | 3            |  $['a'][0]  | Comparisons of    |
|                  | 5            |  $['a'][1]  | primitive and of  |
|                  | 1            |  $['a'][2]  | structured values |
|                  | 2            |  $['a'][3]  |                   |
|                  | 4            |  $['a'][4]  |                   |
|                  | 6            |  $['a'][5]  |                   |
|                  | {"b": "j"}   |  $['a'][6]  |                   |
|                  | {"b": "k"}   |  $['a'][7]  |                   |
|                  | {"b": {}}    |  $['a'][8]  |                   |
|                  | {"b":        |  $['a'][9]  |                   |
|                  | "kilo"}      |             |                   |
+------------------+--------------+-------------+-------------------+

Table 12: Filter Selector Examples

Okay, the big takeaway from that $.o[?@<3, ?@<3] query ๐Ÿคฏ is that a filter selector can hand back nodelists in different orders every time it shows up in a child segment. Literally unpredictable, iykyk ๐ŸŽ€

2.4. Function Extensions โœจ

Okay bestie, so beyond the filter expression stuff we already covered ๐Ÿ’… JSONPath has this extension point where you can add more filter expression functionality, and it's called "Function Extensions" ๐Ÿงš

This section sets up that extension point and defines some function extensions that plug into it ๐ŸŽ€. Even though they're built on the extension point, they're a real, integral part of JSONPath, so implement them like everything else. No side quests ๐Ÿ˜ค

A function extension defines a registered name (see Section 3.2) ๐Ÿฆ‹ that you can apply to zero or more arguments to get a result. Every registered function name is unique, so no twins allowed ๐Ÿ‘ฏ

A function extension MUST be defined so its evaluation is free of side effects. That means every possible order of evaluation, and every choice of short-circuiting or full evaluation of an expression containing it, MUST lead to the same result. (Memoization or logging don't count as side effects here ๐Ÿ™ˆ they're only visible at the implementation level and don't change the result of the evaluation.)

function-name       = function-name-first *function-name-char
function-name-first = LCALPHA
function-name-char  = function-name-first / "_" / DIGIT
LCALPHA             = %x61-7A  ; "a".."z"

function-expr       = function-name "(" S [function-argument
                         *(S "," S function-argument)] S ")"
function-argument   = literal /
                      filter-query / ; (includes singular-query)
                      logical-expr /
                      function-expr

Any function expressions in a query must be well-formed (conforming to the ABNF above) and well-typed. If they're not, the JSONPath implementation MUST raise an error (see Section 2.1) ๐Ÿšจ To figure out which function expressions are well-typed, we first need a type system, so let's go ๐Ÿ“

2.4.1. Type System for Function Expressions

Each parameter and the result of a function extension must have a declared type ๐Ÿ“

Declared types let you check a JSONPath query for well-typedness on its own, without needing to know which query argument it gets applied to. Super clean โœจ

Table 13 defines the available types by the instances they contain ๐Ÿ’–

           +=============+=============================+
           | Type        | Instances                   |
           +=============+=============================+
           | ValueType   | JSON values or Nothing      |
           +-------------+-----------------------------+
           | LogicalType | LogicalTrue or LogicalFalse |
           +-------------+-----------------------------+
           | NodesType   | Nodelists                   |
           +-------------+-----------------------------+

              Table 13: Function Extension Type System

Notes:

  • The only instances you can write directly in JSONPath syntax are certain JSON values in ValueType, written as literals (and in JSONPath, literals are limited to primitive values) ๐Ÿงธ
  • The special result Nothing means "no JSON value here," and it's totally distinct from any JSON value, including null ๐Ÿซฅ
  • LogicalTrue and LogicalFalse have nothing to do with the JSON values written as true and false. Different vibes entirely ๐Ÿ™…โ€โ™€๏ธ

2.4.2. Type Conversion

Remember how queries can be used in logical expressions by checking for the existence of at least one node (Section 2.3.5.2.1)? Well, a function expression with a declared type of NodesType can be used as a function argument for a parameter declared as LogicalType, using this equivalent conversion rule ๐Ÿ”„

  • If the nodelist contains one or more nodes, the conversion result is LogicalTrue โœ…
  • If the nodelist is empty, the conversion result is LogicalFalse โŒ

Notes:

  • Pulling a value out of a nodelist can be done in several different ways, so an implicit conversion from NodesType to ValueType might be surprising. That's why it hasn't been defined ๐Ÿ™ˆ
  • A function expression with a declared type of NodesType can still be used as an argument for a parameter declared as ValueType. You just wrap it in a call to a function extension, like value() (see Section 2.4.8), which takes a NodesType parameter and returns a ValueType result ๐ŸŽ

The well-typedness of function expressions can now be defined in terms of this type system ๐Ÿงšโ€โ™€๏ธ

2.4.3. Well-Typedness of Function Expressions

For a function expression to be well-typed:

  1. Its declared type must be well-typed in the context where it occurs.

As per the grammar, a function expression can show up in three different immediate contexts, and each one leads to its own well-typedness conditions ๐Ÿ‘€

As a test-expr in a logical expression: The function's declared result type is LogicalType or (giving rise to conversion as per Section 2.4.2) NodesType.

As a comparable in a comparison: The function's declared result type is ValueType.

As a function-argument in another function expression: The function's declared result type fulfills the following rules for the corresponding parameter of the enclosing function.

  1. Its arguments must be well-typed for the declared type of the corresponding parameters.

The arguments of the function expression are well-typed when each argument can be used for the declared type of the corresponding parameter, according to one of the following conditions:

  • When the argument is a function expression with the same declared result type as the declared type of the parameter ๐Ÿซถ
  • When the declared type of the parameter is LogicalType and the argument is one of the following:
    • A function expression with declared result type NodesType. In this case, the argument is converted to LogicalType as per Section 2.4.2.
    • A logical-expr that is not a function expression.
  • When the declared type of the parameter is NodesType and the argument is a query (which includes singular query).
  • When the declared type of the parameter is ValueType and the argument is one of the following:
    • A value expressed as a literal.
    • A singular query. In this case:
      • If the query results in a nodelist consisting of a single node, the argument is the value of the node.
      • If the query results in an empty nodelist, the argument is the special result Nothing.

2.4.4. length() Function Extension

Parameters:

  1. ValueType

Result: ValueType (unsigned integer or Nothing)

The length() function extension gives you a way to compute the length of a value and make that available for further processing in the filter expression ๐Ÿ“โœจ

$[?length(@.authors) >= 5]

Its only argument is an instance of ValueType (possibly taken from a singular query, like in the example above). The result is also an instance of ValueType: an unsigned integer or the special result Nothing ๐Ÿ”ข

  • If the argument value is a string, the result is the number of Unicode scalar values in the string ๐Ÿ”ค
  • If the argument value is an array, the result is the number of elements in the array ๐Ÿ“š
  • If the argument value is an object, the result is the number of members in the object ๐Ÿงบ
  • For any other argument value, the result is the special result Nothing ๐Ÿซฅ

2.4.5. count() Function Extension

Parameters:

  1. NodesType

Result: ValueType (unsigned integer)

The count() function extension gives you a way to get the number of nodes in a nodelist and make that available for further processing in the filter expression ๐Ÿงฎ๐Ÿ’…

$[?count(@.*.author) >= 5]

Its only argument is a nodelist. The result is a value (an unsigned integer) giving the number of nodes in the nodelist ๐ŸŽ€

Notes:

  • There is no deduplication of the nodelist. Duplicates stay, bestie ๐Ÿ‘ฏ
  • The nodes are counted regardless of their values or any children they may have. So the count of a non-empty singular nodelist, like count(@), is always 1 โ˜๏ธ

2.4.6. match() Function Extension

Parameters:

  1. ValueType (string)
  2. ValueType (string conforming to [RFC9485])

Result: LogicalType

The match() function extension lets you check whether (the entirety of; see Section 2.4.7) a given string matches a given regular expression, written in the form described in [RFC9485] ๐Ÿ”๐Ÿฆ‹

$[?match(@.date, "1974-05-..")]

Its arguments are instances of ValueType (possibly taken from a singular query, like the first argument in the example above). If the first argument isn't a string, or the second argument isn't a string conforming to [RFC9485], the result is LogicalFalse ๐Ÿ™…โ€โ™€๏ธ Otherwise, the string that is the first argument gets matched against the I-Regexp contained in the string that is the second argument. The result is LogicalTrue if the string matches the I-Regexp, and LogicalFalse otherwise ๐Ÿ’œ

2.4.7. search() Function Extension

Parameters:

  1. ValueType (string)
  2. ValueType (string conforming to [RFC9485])

Result: LogicalType

The search() function extension lets you check whether a given string contains a substring that matches a given regular expression, written in the form described in [RFC9485] ๐Ÿ•ต๏ธโ€โ™€๏ธ

$[?search(@.author, "[BR]ob")]

Its arguments are instances of ValueType (possibly taken from a singular query, like the first argument in the example above). If the first argument isn't a string, or the second argument isn't a string conforming to [RFC9485], the result is LogicalFalse ๐Ÿ™…โ€โ™€๏ธ Otherwise, the string that is the first argument gets searched for a substring that matches the I-Regexp contained in the string that is the second argument. The result is LogicalTrue if at least one such substring exists, and LogicalFalse otherwise ๐ŸŒธ

2.4.8. value() Function Extension

Parameters:

  1. NodesType

Result: ValueType

The value() function extension gives you a way to convert an instance of NodesType into a value and make that available for further processing in the filter expression ๐Ÿช„

$[?value(@..color) == "red"]

Its only argument is an instance of NodesType (possibly taken from a filter-query, like in the example above). The result is an instance of ValueType ๐ŸŽจ

  • If the argument contains a single node, the result is the value of that node ๐Ÿฅ‡
  • If the argument is the empty nodelist or contains multiple nodes, the result is Nothing ๐Ÿซฅ

Note: A singular query can be used anywhere a ValueType is expected, so there's no need to use the value() function extension with a singular query. Efficient and iconic ๐Ÿ‘‘

2.4.9. Examples โœจ

okay bestie, this table is literally a cheat sheet for function expressions ๐Ÿ’… some are well-typed (slay) and some are not (not me making these mistakes ๐Ÿ’€)

+======================+==========================================+
|        Query         | Comment                                  |
+======================+==========================================+
|  $[?length(@) < 3]   | well-typed                               |
+----------------------+------------------------------------------+
| $[?length(@.*) < 3]  | not well-typed since @.* is a non-       |
|                      | singular query                           |
+----------------------+------------------------------------------+
| $[?count(@.*) == 1]  | well-typed                               |
+----------------------+------------------------------------------+
|  $[?count(1) == 1]   | not well-typed since 1 is not a query or |
|                      | function expression                      |
+----------------------+------------------------------------------+
|  $[?count(foo(@.*))  | well-typed, where foo() is a function    |
|        == 1]         | extension with a parameter of type       |
|                      | NodesType and result type NodesType      |
+----------------------+------------------------------------------+
| $[?match(@.timezone, | well-typed                               |
|    'Europe/.*')]     |                                          |
+----------------------+------------------------------------------+
| $[?match(@.timezone, | not well-typed as LogicalType may not be |
|   'Europe/.*') ==    | used in comparisons                      |
|        true]         |                                          |
+----------------------+------------------------------------------+
|  $[?value(@..color)  | well-typed                               |
|      == "red"]       |                                          |
+----------------------+------------------------------------------+
| $[?value(@..color)]  | not well-typed as ValueType may not be   |
|                      | used in a test expression                |
+----------------------+------------------------------------------+
|     $[?bar(@.a)]     | well-typed for any function bar() with a |
|                      | parameter of any declared type and       |
|                      | result type LogicalType                  |
+----------------------+------------------------------------------+
|     $[?bnl(@.*)]     | well-typed for any function bnl() with a |
|                      | parameter of declared type NodesType or  |
|                      | LogicalType and result type LogicalType  |
+----------------------+------------------------------------------+
|    $[?blt(1==1)]     | well-typed, where blt() is a function    |
|                      | with a parameter of declared type        |
|                      | LogicalType and result type LogicalType  |
+----------------------+------------------------------------------+
|      $[?blt(1)]      | not well-typed for the same function     |
|                      | blt(), as 1 is not a query, logical-     |
|                      | expr, or function expression             |
+----------------------+------------------------------------------+
|      $[?bal(1)]      | well-typed, where bal() is a function    |
|                      | with a parameter of declared type        |
|                      | ValueType and result type LogicalType    |
+----------------------+------------------------------------------+

               Table 14: Function Expression Examples

2.5. Segments ๐ŸŒˆ

okay so, for every node in an input nodelist, segments take one or more selectors and run them on that node ๐Ÿ’… each selector's results get bundled into a per-input-node nodelist, and then all those bundles get stuck together in input-nodelist order to make one big segment result nodelist โœจ

it turns out the more segments a query has, the deeper into the input value its results go ๐ŸŒŠ

  • a query with N segments, where N >= 0, gives you a nodelist of nodes at depth N or greater in the input value ๐Ÿซถ
  • a query with N segments, where N >= 0, where every one of them is a child segment (Section 2.5.1), gives you nodes at exactly depth N in the input value ๐ŸŽฏ

there are two kinds of segments: child segments and descendant segments ๐Ÿฆ‹

segment             = child-segment / descendant-segment

the syntax and semantics of each kind are spelled out below, so keep scrolling bestie ๐Ÿ‘‘

2.5.1. Child Segment ๐Ÿงš

2.5.1.1. Syntax

a child segment is a non-empty, comma-separated sequence of selectors wrapped in square brackets ๐Ÿ“ there are also shorthands for when you only have a single wildcard or name selector, which is very convenient ๐Ÿ’–

child-segment       = bracketed-selection /
                      ("."
                       (wildcard-selector /
                        member-name-shorthand))

bracketed-selection = "[" S selector *(S "," S selector) S "]"

member-name-shorthand = name-first *name-char
name-first          = ALPHA /
                      "_"   /
                      %x80-D7FF /
                         ; skip surrogate code points
                      %xE000-10FFFF
name-char           = name-first / DIGIT

DIGIT               = %x30-39              ; 0-9
ALPHA               = %x41-5A / %x61-7A    ; A-Z / a-z

so .* is a child segment built straight from a wildcard selector, and it's just shorthand for [*] ๐ŸŒธ

and .<member-name> is a child segment built from a member-name shorthand, which is shorthand for ['<member-name>'] ๐Ÿ’… Note: this only works with member names made of certain characters, as laid out in the ABNF rule member-name-shorthand. so $.foo.bar is shorthand for $['foo']['bar'], but NOT for $['foo.bar'] ๐Ÿ™ˆ

2.5.1.2. Semantics

a child segment holds a sequence of selectors, and each one picks zero or more children of the input value ๐ŸŽ€

you can mix different kinds of selectors inside a single child segment, no cap ๐Ÿ’œ

for each node in the input nodelist, the resulting nodelist of a child segment is the concatenation of the nodelists from each of its selectors, in the order the selectors show up in the list ๐Ÿ”ฅ Note: any node matched by more than one selector is kept as many times in the nodelist, so duplicates stay ๐Ÿ’€

when a selector can produce its nodelist in more than one possible order, each time that selector shows up in the child segment it may come out in a different order ๐ŸŒˆ

in summary, a child segment drills down one more level into the structure of the input value ๐Ÿซถ

2.5.1.3. Examples

JSON:

["a", "b", "c", "d", "e", "f", "g"]

Queries:

+========+========+========+============+
| Query  | Result | Result | Comment    |
|        |        | Paths  |            |
+========+========+========+============+
|  $[0,  | "a"    |  $[0]  | Indices    |
|   3]   | "d"    |  $[3]  |            |
+--------+--------+--------+------------+
| $[0:2, | "a"    |  $[0]  | Slice and  |
|   5]   | "b"    |  $[1]  | index      |
|        | "f"    |  $[5]  |            |
+--------+--------+--------+------------+
|  $[0,  | "a"    |  $[0]  | Duplicated |
|   0]   | "a"    |  $[0]  | entries    |
+--------+--------+--------+------------+

                Table 15: Child Segment Examples

2.5.2. Descendant Segment ๐Ÿฆ‹

2.5.2.1. Syntax

a descendant segment is a double dot .. followed by a child segment, written in bracket notation ๐Ÿง there are shorthands too, matching the shorthand forms of the child segment ๐Ÿ’–

descendant-segment  = ".." (bracketed-selection /
                            wildcard-selector /
                            member-name-shorthand)

..*, a descendant segment built straight from a wildcard selector, is shorthand for ..[*] โœจ

..<member-name>, a descendant segment built from a member-name shorthand, is shorthand for ..['<member-name>'] ๐Ÿ’… Note: just like the child segment shorthand, this only works with member names made of certain characters, as laid out in the ABNF rule member-name-shorthand.

Note: on its own, .. is not a valid segment ๐Ÿ™…โ€โ™€๏ธ

2.5.2.2. Semantics

a descendant segment produces zero or more descendants of an input value ๐ŸŒŠ

for each node in the input nodelist, a descendant selector visits the input node and each of its descendants, following these rules:

  • nodes of any array are visited in array order, and
  • nodes are visited before their descendants ๐Ÿซง

the order in which an object's children get visited isn't specified, since JSON objects are unordered ๐Ÿคทโ€โ™€๏ธ

suppose the descendant segment is of the form ..[<selectors>] (after converting any shorthand form to bracket notation), and the nodes, in the order visited, are D1, ..., Dn (where n >= 1). Note: D1 is the input value ๐Ÿ‘‘

for each i such that 1 <= i <= n, the nodelist Ri is defined to be a result of applying the child segment [<selectors>] to the node Di ๐Ÿงš

for each node in the input nodelist, the result of the descendant segment is the concatenation of R1, ..., Rn, in that order. these results are then concatenated in input nodelist order to form the result of the segment ๐Ÿ’œ

in summary, a descendant segment drills down one or more levels into the structure of each input value ๐ŸŒˆ

2.5.2.3. Examples โœจ

okay bestie, time for the examples ๐Ÿ’– here's the JSON we're working with:

{
  "o": {"j": 1, "k": 2},
  "a": [5, 3, [{"j": 4}, {"k": 6}]]
}

Now the queries ๐Ÿฆ‹ (fair warning: the fourth example can be written two equivalent ways, and Table 16 squishes them into one row instead of two almost-identical rows, because we're efficient like that ๐Ÿ’…)

+==========+================+===================+===================+
|  Query   | Result         |    Result Paths   | Comment           |
+==========+================+===================+===================+
|   $..j   | 1              |    $['o']['j']    | Object values     |
|          | 4              | $['a'][2][0]['j'] |                   |
+----------+----------------+-------------------+-------------------+
|   $..j   | 4              | $['a'][2][0]['j'] | Alternative       |
|          | 1              |    $['o']['j']    | result            |
+----------+----------------+-------------------+-------------------+
|  $..[0]  | 5              |     $['a'][0]     | Array values      |
|          | {"j": 4}       |    $['a'][2][0]   |                   |
+----------+----------------+-------------------+-------------------+
|  $..[*]  | {"j": 1,       |       $['o']      | All values        |
|    or    | "k": 2}        |       $['a']      |                   |
|   $..*   | [5, 3,         |    $['o']['j']    |                   |
|          | [{"j": 4},     |    $['o']['k']    |                   |
|          | {"k": 6}]]     |     $['a'][0]     |                   |
|          | 1              |     $['a'][1]     |                   |
|          | 2              |     $['a'][2]     |                   |
|          | 5              |    $['a'][2][0]   |                   |
|          | 3              |    $['a'][2][1]   |                   |
|          | [{"j": 4},     | $['a'][2][0]['j'] |                   |
|          | {"k": 6}]      | $['a'][2][1]['k'] |                   |
|          | {"j": 4}       |                   |                   |
|          | {"k": 6}       |                   |                   |
|          | 4              |                   |                   |
|          | 6              |                   |                   |
+----------+----------------+-------------------+-------------------+
|   $..o   | {"j": 1,       |       $['o']      | Input value is    |
|          | "k": 2}        |                   | visited           |
+----------+----------------+-------------------+-------------------+
| $.o..[*, | 1              |    $['o']['j']    | Non-deterministic |
|    *]    | 2              |    $['o']['k']    | ordering          |
|          | 2              |    $['o']['k']    |                   |
|          | 1              |    $['o']['j']    |                   |
+----------+----------------+-------------------+-------------------+
| $.a..[0, | 5              |     $['a'][0]     | Multiple segments |
|    1]    | 3              |     $['a'][1]     |                   |
|          | {"j": 4}       |    $['a'][2][0]   |                   |
|          | {"k": 6}       |    $['a'][2][1]   |                   |
+----------+----------------+-------------------+-------------------+

                   Table 16: Descendant Segment Examples

Note: the ordering of the results for the $..[*] and $..* examples above is not guaranteed, except that:

  • {"j": 1, "k": 2} must appear before 1 and 2,
  • [5, 3, [{"j": 4}, {"k": 6}]] must appear before 5, 3, and [{"j": 4}, {"k": 6}],
  • 5 must appear before 3, which must appear before [{"j": 4}, {"k": 6}],
  • 5 and 3 must appear before {"j": 4}, 4, {"k": 6}, and 6,
  • [{"j": 4}, {"k": 6}] must appear before {"j": 4} and {"k": 6},
  • {"j": 4} must appear before {"k": 6},
  • {"k": 6} must appear before 4, and
  • 4 must appear before 6.

The example above with the query $.o..[*, *] shows that a selector may produce nodelists in distinct orders each time it appears in the descendant segment. ๐ŸŒ€

The example above with the query $.a..[0, 1] shows that the child segment [0, 1] is applied to each node in turn ๐Ÿซถ (rather than the nodes being visited once per selector, which is the case for some JSONPath implementations that do not conform to this specification).


2.6. Semantics of null ๐Ÿ’œ

Note: JSON null is treated the same as any other JSON value, i.e., it is not taken to mean "undefined" or "missing". ๐Ÿ™ˆ

2.6.1. Examples

JSON, bestie:

{"a": null, "b": [null], "c": [{}], "null": 1}

Queries:

+=================+========+===========+===========================+
|      Query      | Result |   Result  | Comment                   |
|                 |        |   Paths   |                           |
+=================+========+===========+===========================+
|       $.a       | null   |   $['a']  | Object value              |
+-----------------+--------+-----------+---------------------------+
|      $.a[0]     |        |           | null used as array        |
+-----------------+--------+-----------+---------------------------+
|      $.a.d      |        |           | null used as object       |
+-----------------+--------+-----------+---------------------------+
|      $.b[0]     | null   | $['b'][0] | Array value               |
+-----------------+--------+-----------+---------------------------+
|      $.b[*]     | null   | $['b'][0] | Array value               |
+-----------------+--------+-----------+---------------------------+
|     $.b[?@]     | null   | $['b'][0] | Existence                 |
+-----------------+--------+-----------+---------------------------+
|  $.b[?@==null]  | null   | $['b'][0] | Comparison                |
+-----------------+--------+-----------+---------------------------+
| $.c[?@.d==null] |        |           | Comparison with "missing" |
|                 |        |           | value                     |
+-----------------+--------+-----------+---------------------------+
|      $.null     | 1      | $['null'] | Not JSON null at all,     |
|                 |        |           | just a member name string |
+-----------------+--------+-----------+---------------------------+

           Table 17: Examples Involving (or Not Involving) null

2.7. Normalized Paths ๐ŸŽ€

okay so a Normalized Path is a unique address for a node in a value, and it pins down exactly one node ๐Ÿฆ‹ Specifically, it's a JSONPath query with a restricted syntax (defined below), e.g., $['book'][3]. When you apply it to the value, you get a nodelist with just the one node it identifies. Note: a Normalized Path represents the identity of a node in a specific value. There is precisely one Normalized Path identifying any particular node in a value. ๐Ÿงš

A nodelist can be represented compactly in JSON as an array of strings, where the strings are Normalized Paths. ๐Ÿ“

Normalized Paths give you a predictable format, which makes testing and post-processing of nodelists way easier, e.g., removing duplicate nodes โœจ That's why they're used as the result paths in this document's examples.

Normalized Paths use the canonical bracket notation, not dot notation. ๐Ÿ’…

Single quotes are used to delimit string member names in Normalized Paths. This cuts down on how many characters need escaping when Normalized Paths show up in strings delimited by double quotes, e.g., in JSON texts. ๐ŸŒˆ

Certain characters are escaped in Normalized Paths in one and only one way, and all other characters are unescaped. ๐Ÿ’€

Note: Normalized Paths are singular queries, but not all singular queries are Normalized Paths. For example, $[-3] is a singular query but is not a Normalized Path. The Normalized Path equivalent to $[-3] would have an index equal to the array length minus 3. (The array length must be at least 3 if $[-3] is to identify a node.) ๐Ÿ™ˆ

Here's the grammar, verbatim, no cap:

normalized-path      = root-identifier *(normal-index-segment)
normal-index-segment = "[" normal-selector "]"
normal-selector      = normal-name-selector / normal-index-selector
normal-name-selector = %x27 *normal-single-quoted %x27 ; 'string'
normal-single-quoted = normal-unescaped /
                       ESC normal-escapable
normal-unescaped     =    ; omit %x0-1F control codes
                       %x20-26 /
                          ; omit 0x27 '
                       %x28-5B /
                          ; omit 0x5C \
                       %x5D-D7FF /
                          ; skip surrogate code points
                       %xE000-10FFFF

normal-escapable     = %x62 / ; b BS backspace U+0008
                       %x66 / ; f FF form feed U+000C
                       %x6E / ; n LF line feed U+000A
                       %x72 / ; r CR carriage return U+000D
                       %x74 / ; t HT horizontal tab U+0009
                       "'" /  ; ' apostrophe U+0027
                       "\" /  ; \ backslash (reverse solidus) U+005C
                       (%x75 normal-hexchar)
                                       ; certain values u00xx U+00XX
normal-hexchar       = "0" "0"
                       (
                          ("0" %x30-37) / ; "00"-"07"
                             ; omit U+0008-U+000A BS HT LF
                          ("0" %x62) /    ; "0b"
                             ; omit U+000C-U+000D FF CR
                          ("0" %x65-66) / ; "0e"-"0f"
                          ("1" normal-HEXDIG)
                       )
normal-HEXDIG        = DIGIT / %x61-66    ; "0"-"9", "a"-"f"
normal-index-selector = "0" / (DIGIT1 *DIGIT)
                        ; non-negative decimal integer

Since there can only be one Normalized Path identifying a given node, the syntax decides which characters get escaped and which don't. So the definition of normal-hexchar is built for hex escaping characters that aren't easy to print, like U+000B LINE TABULATION, where there's no standard JSON escape such as \n to use. ๐Ÿง 

2.7.1. Examples

+=============+=================+==========================+
|     Path    | Normalized Path | Comment                  |
+=============+=================+==========================+
|     $.a     |      $['a']     | Object value             |
+-------------+-----------------+--------------------------+
|     $[1]    |       $[1]      | Array index              |
+-------------+-----------------+--------------------------+
|    $[-3]    |       $[2]      | Negative array index for |
|             |                 | an array of length 5     |
+-------------+-----------------+--------------------------+
|  $.a.b[1:2] |  $['a']['b'][1] | Nested structure         |
+-------------+-----------------+--------------------------+
| $["\u000B"] |   $['\u000b']   | Unicode escape           |
+-------------+-----------------+--------------------------+
| $["\u0061"] |      $['a']     | Unicode character        |
+-------------+-----------------+--------------------------+

                Table 18: Normalized Path Examples

3. IANA Considerations โœจ

3.1. Registration of Media Type application/jsonpath

okay bestie, IANA has officially registered a brand new media type for JSONPath ๐Ÿฆ‹โœจ here's the whole registration template, no cap:

  • Type name: application
  • Subtype name: jsonpath
  • Required parameters: N/A
  • Optional parameters: N/A
  • Encoding considerations: binary (UTF-8)
  • Security considerations: See the Security Considerations section of RFC 9535. ๐Ÿ”
  • Interoperability considerations: N/A
  • Published specification: RFC 9535
  • Applications that use this media type: Applications that need to convey queries in JSON data ๐Ÿ’–
  • Fragment identifier considerations: N/A
  • Additional information:
    • Deprecated alias names for this type: N/A
    • Magic number(s): N/A
    • File extension(s): N/A
    • Macintosh file type code(s): N/A
  • Person & email address to contact for further information: iesg@ietf.org ๐Ÿ“ฌ
  • Intended usage: COMMON
  • Restrictions on usage: N/A
  • Author: JSONPath WG
  • Change controller: IETF

3.2. Function Extensions Subregistry

so here's the vibe ๐Ÿ’… IANA made a brand new "Function Extensions" subregistry inside a brand new "JSONPath" registry. The policy for it is "Expert Review" (Section 4.5 of [RFC8126]), which means a real expert gets to look at each request.

the experts are told to be frugal ๐ŸŒธ with names that sound like they could be generally applicable. those pretty names are kept in reserve for functions that will probably get tons of use and can really benefit from being short and sweet. the expert is also told to ask the registrant for a specification (Section 4.6 of [RFC8126]), but they're allowed to make exceptions, like when a spec isn't ready yet at registration time but is clearly on its way. and if the expert spots function extensions that are already deployed and in use, they can start a registration on their own if they think it'll stop future name collisions ๐Ÿ™ˆ

every entry in the subregistry needs the following stuff:

  • Function Name: a lowercase ASCII [RFC0020] string that starts with a letter and can have letters, digits, and underscores after that ([a-z][_a-z0-9]*). No other entry can share the same function name, so be unique, bestie ๐Ÿ‘‘
  • Brief description: a brief description
  • Parameters: a comma-separated list of zero or more declared types, one for each argument this function extension expects ๐Ÿงš
  • Result: the declared type of the result for this function extension
  • Change Controller: see Section 2.3 of [RFC8126]
  • Reference: a reference document that describes the function extension ๐Ÿ“–

the initial entries are listed in Table 19 ๐Ÿ“ the "Change Controller" column is "IETF" for all of them, and the "Reference" column is "Section 2.4 of RFC 9535" for all of them too:

+===============+=====================+============+=============+
| Function Name | Brief Description   | Parameters | Result      |
+===============+=====================+============+=============+
| length        | length of string,   | ValueType  | ValueType   |
|               | array, or object    |            |             |
+---------------+---------------------+------------+-------------+
| count         | size of nodelist    | NodesType  | ValueType   |
+---------------+---------------------+------------+-------------+
| match         | regular expression  | ValueType, | LogicalType |
|               | full match          | ValueType  |             |
+---------------+---------------------+------------+-------------+
| search        | regular expression  | ValueType, | LogicalType |
|               | substring match     | ValueType  |             |
+---------------+---------------------+------------+-------------+
| value         | value of the single | NodesType  | ValueType   |
|               | node in nodelist    |            |             |
+---------------+---------------------+------------+-------------+

 Table 19: Initial Entries in the Function Extensions Subregistry

4. Security Considerations ๐Ÿ”’

okay, serious moment, bestie ๐Ÿ˜ญ security for JSONPath can come from three places:

  • attack vectors on JSONPath implementations,
  • attack vectors on how JSONPath queries are formed, and
  • the way JSONPath is used in security-relevant mechanisms.

4.1. Attack Vectors on JSONPath Implementations

historically, people implemented JSONPath by feeding bits of the query into an underlying programming language engine, like JavaScript's eval() function ๐Ÿ’€ this is well known to lead to injection attacks, and stopping those would need perfect input validation (see Section 12 of [RFC8259] for similar considerations for JSON itself). so instead, JSONPath implementations need to implement the entire syntax of the query themselves, without leaning on the parsers of programming language engines. no outsourcing the vibes ๐Ÿ’…

attacks on availability might try to trigger unusually expensive runtime performance that certain implementations show in certain cases. (See Section 10 of [RFC8949] for issues in hash-table implementations and Section 8 of [RFC9485] for performance issues in regular expression implementations.) implementers need to be aware that good average performance is not enough as long as an attacker can choose to submit specially crafted JSONPath queries or query arguments that trigger surprisingly high, possibly exponential, CPU usage. a naive recursive implementation of the descendant segment could also lead to stack overflow, for example ๐ŸŒ€ implementations need appropriate resource management to mitigate these attacks ๐Ÿ›ก๏ธ

4.2. Attack Vectors on How JSONPath Queries Are Formed

JSONPath queries are often not static. they're formed from variables that provide index values, member names, or values to compare with in a filter expression โœจ these variables need to be validated (e.g., only allowing specific constructs such as .name to be formed when the given values allow that) and translated (e.g., by escaping string delimiters). not doing these validations and translations correctly can lead to unexpected failures, and those can lead to availability, confidentiality, and integrity breaches ๐Ÿ˜ฑ this is especially true if an adversary controls the values (e.g., by entering them into a web form). this class of attacks, injections (e.g., SQL injections), is consistently found among the top causes of application security vulnerabilities, so it needs particular attention ๐Ÿšจ

4.3. Attacks on Security Mechanisms That Employ JSONPath

where JSONPath is part of a security mechanism, attackers can try to provoke unexpected or unpredictable behavior, or take advantage of differences in behavior between JSONPath implementations ๐Ÿ‘€

unexpected or unpredictable behavior can come from a query argument with certain constructs that [RFC8259] describes as unpredictable. predictable behavior can be expected, except for the ordering of objects, for any query argument that conforms with [RFC7493] ๐Ÿ’œ

other attacks can target the behavior of underlying technologies, such as UTF-8 (see Section 10 of [RFC3629]) and the Unicode character set.


5. References

5.1. Normative References

5.2. Informative References ๐Ÿ“š

okay bestie, these are the extra reading links ๐Ÿค“ You don't need them to build the thing, but they're the backstory and the receipts, so bookmark them ๐Ÿ’œ


Appendix A. Collected ABNF Grammars ๐Ÿงš

this appendix is the greatest hits album ๐ŸŽ€ it pulls together every ABNF grammar we've been using throughout the doc so you can find them all in one spot ๐Ÿ’…

Figure 2 has the collected ABNF grammar that defines the syntax of a JSONPath query โœจ

jsonpath-query      = root-identifier segments
segments            = *(S segment)

B                   = %x20 /    ; Space
                      %x09 /    ; Horizontal tab
                      %x0A /    ; Line feed or New line
                      %x0D      ; Carriage return
S                   = *B        ; optional blank space
root-identifier     = "$"
selector            = name-selector /
                      wildcard-selector /
                      slice-selector /
                      index-selector /
                      filter-selector
name-selector       = string-literal

string-literal      = %x22 *double-quoted %x22 /     ; "string"
                      %x27 *single-quoted %x27       ; 'string'

double-quoted       = unescaped /
                      %x27      /                    ; '
                      ESC %x22  /                    ; \"
                      ESC escapable

single-quoted       = unescaped /
                      %x22      /                    ; "
                      ESC %x27  /                    ; \'
                      ESC escapable

ESC                 = %x5C                           ; \ backslash

unescaped           = %x20-21 /                      ; see RFC 8259
                         ; omit 0x22 "
                      %x23-26 /
                         ; omit 0x27 '
                      %x28-5B /
                         ; omit 0x5C \
                      %x5D-D7FF /
                         ; skip surrogate code points
                      %xE000-10FFFF

escapable           = %x62 / ; b BS backspace U+0008
                      %x66 / ; f FF form feed U+000C
                      %x6E / ; n LF line feed U+000A
                      %x72 / ; r CR carriage return U+000D
                      %x74 / ; t HT horizontal tab U+0009
                      "/"  / ; / slash (solidus) U+002F
                      "\"  / ; \ backslash (reverse solidus) U+005C
                      (%x75 hexchar) ;  uXXXX U+XXXX

hexchar             = non-surrogate /
                      (high-surrogate "\" %x75 low-surrogate)
non-surrogate       = ((DIGIT / "A"/"B"/"C" / "E"/"F") 3HEXDIG) /
                      ("D" %x30-37 2HEXDIG )
high-surrogate      = "D" ("8"/"9"/"A"/"B") 2HEXDIG
low-surrogate       = "D" ("C"/"D"/"E"/"F") 2HEXDIG

HEXDIG              = DIGIT / "A" / "B" / "C" / "D" / "E" / "F"
wildcard-selector   = "*"
index-selector      = int                        ; decimal integer

int                 = "0" /
                      (["-"] DIGIT1 *DIGIT)      ; - optional
DIGIT1              = %x31-39                    ; 1-9 non-zero digit
slice-selector      = [start S] ":" S [end S] [":" [S step ]]

start               = int       ; included in selection
end                 = int       ; not included in selection
step                = int       ; default: 1
filter-selector     = "?" S logical-expr
logical-expr        = logical-or-expr
logical-or-expr     = logical-and-expr *(S "||" S logical-and-expr)
                        ; disjunction
                        ; binds less tightly than conjunction
logical-and-expr    = basic-expr *(S "&&" S basic-expr)
                        ; conjunction
                        ; binds more tightly than disjunction

basic-expr          = paren-expr /
                      comparison-expr /
                      test-expr

paren-expr          = [logical-not-op S] "(" S logical-expr S ")"
                                        ; parenthesized expression
logical-not-op      = "!"               ; logical NOT operator
test-expr           = [logical-not-op S]
                      (filter-query / ; existence/non-existence
                       function-expr) ; LogicalType or NodesType
filter-query        = rel-query / jsonpath-query
rel-query           = current-node-identifier segments
current-node-identifier = "@"
comparison-expr     = comparable S comparison-op S comparable
literal             = number / string-literal /
                      true / false / null
comparable          = literal /
                      singular-query / ; singular query value
                      function-expr    ; ValueType
comparison-op       = "==" / "!=" /
                      "<=" / ">=" /
                      "<"  / ">"

singular-query      = rel-singular-query / abs-singular-query
rel-singular-query  = current-node-identifier singular-query-segments
abs-singular-query  = root-identifier singular-query-segments
singular-query-segments = *(S (name-segment / index-segment))
name-segment        = ("[" name-selector "]") /
                      ("." member-name-shorthand)
index-segment       = "[" index-selector "]"
number              = (int / "-0") [ frac ] [ exp ] ; decimal number
frac                = "." 1*DIGIT                  ; decimal fraction
exp                 = "e" [ "-" / "+" ] 1*DIGIT    ; decimal exponent
true                = %x74.72.75.65                ; true
false               = %x66.61.6c.73.65             ; false
null                = %x6e.75.6c.6c                ; null
function-name       = function-name-first *function-name-char
function-name-first = LCALPHA
function-name-char  = function-name-first / "_" / DIGIT
LCALPHA             = %x61-7A  ; "a".."z"

function-expr       = function-name "(" S [function-argument
                         *(S "," S function-argument)] S ")"
function-argument   = literal /
                      filter-query / ; (includes singular-query)
                      logical-expr /
                      function-expr
segment             = child-segment / descendant-segment
child-segment       = bracketed-selection /
                      ("."
                       (wildcard-selector /
                        member-name-shorthand))

bracketed-selection = "[" S selector *(S "," S selector) S "]"

member-name-shorthand = name-first *name-char
name-first          = ALPHA /
                      "_"   /
                      %x80-D7FF /
                         ; skip surrogate code points
                      %xE000-10FFFF
name-char           = name-first / DIGIT

DIGIT               = %x30-39              ; 0-9
ALPHA               = %x41-5A / %x61-7A    ; A-Z / a-z
descendant-segment  = ".." (bracketed-selection /
                            wildcard-selector /
                            member-name-shorthand)

Figure 2: Collected ABNF of JSONPath Queries

Figure 3 is the one for JSONPath Normalized Paths ๐ŸŒธ It uses the rules root-identifier, ESC, DIGIT, and DIGIT1 from Figure 2, so you already know those bestie ๐Ÿซถ

normalized-path      = root-identifier *(normal-index-segment)
normal-index-segment = "[" normal-selector "]"
normal-selector      = normal-name-selector / normal-index-selector
normal-name-selector = %x27 *normal-single-quoted %x27 ; 'string'
normal-single-quoted = normal-unescaped /
                       ESC normal-escapable
normal-unescaped     =    ; omit %x0-1F control codes
                       %x20-26 /
                          ; omit 0x27 '
                       %x28-5B /
                          ; omit 0x5C \
                       %x5D-D7FF /
                          ; skip surrogate code points
                       %xE000-10FFFF

normal-escapable     = %x62 / ; b BS backspace U+0008
                       %x66 / ; f FF form feed U+000C
                       %x6E / ; n LF line feed U+000A
                       %x72 / ; r CR carriage return U+000D
                       %x74 / ; t HT horizontal tab U+0009
                       "'" /  ; ' apostrophe U+0027
                       "\" /  ; \ backslash (reverse solidus) U+005C
                       (%x75 normal-hexchar)
                                       ; certain values u00xx U+00XX
normal-hexchar       = "0" "0"
                       (
                          ("0" %x30-37) / ; "00"-"07"
                             ; omit U+0008-U+000A BS HT LF
                          ("0" %x62) /    ; "0b"
                             ; omit U+000C-U+000D FF CR
                          ("0" %x65-66) / ; "0e"-"0f"
                          ("1" normal-HEXDIG)
                       )
normal-HEXDIG        = DIGIT / %x61-66    ; "0"-"9", "a"-"f"
normal-index-selector = "0" / (DIGIT1 *DIGIT)
                        ; non-negative decimal integer

Figure 3: Collected ABNF of JSONPath Normalized Paths

Appendix B. Inspired by XPath โœจ

This appendix is informative, so it's vibes and context, not the rules ๐Ÿ’…

At the time JSONPath was invented, XML was known for having powerful tools to analyze, transform, and selectively pull data out of XML documents. [XPath] is one of those tools ๐Ÿฆ‹

By 2007, the JSON community needed something that solved the same kind of problems, mainly for two things:

  • finding data interactively and pulling it out of JSON values [RFC8259] without special scripting, and
  • letting a client specify the relevant parts of the JSON data in a request, so the server can send back less data in its response and save bandwidth ๐ŸŒธ
Note: XPath has evolved since 2007, and recent versions even nominally support operating inside JSON values. This appendix only discusses the more widely used version of XPath that was available in 2007.

JSONPath keeps the overall feeling of XPath but maps the concepts onto syntax (and partly semantics) that someone using JSON in a dynamic language would already find familiar ๐Ÿงš

For example, in popular dynamic languages like JavaScript, Python, and PHP, the XPath expression:

/store/book[1]/title

can be written as:

x.store.book[0].title

or in bracket notation:

x['store']['book'][0]['title']

where the variable x holds the query argument.

The JSONPath language was designed to:

  • be naturally based on those language characteristics,
  • cover only the most essential parts of XPath 1.0,
  • be lightweight in code size and memory consumption, and
  • be runtime efficient โšก

B.1. JSONPath and XPath

JSONPath expressions apply to JSON values the same way XPath expressions get used with an XML document ๐Ÿ’– JSONPath uses $ to refer to the root node of the query argument, kind of like XPath's / at the front.

JSONPath expressions move further down the hierarchy using _dot notation_ ($.store.book[0].title) or _bracket notation_ ($['store']['book'][0]['title']). Both of them replace XPath's / inside query expressions. Dot notation is a lightweight but limited syntax, while bracket notation is heavyweight but more general ๐ŸŽ€

Both JSONPath and XPath use * as a wildcard. JSONPath's descendant segment notation, which starts with .., was borrowed from [E4X] and is similar to XPath's //. The array slicing construct [start:end:step] is unique to JSONPath and was inspired by [SLICE] from ECMASCRIPT 4 โœจ

Filter expressions are supported through the syntax ?<logical-expr>, like this:

$.store.book[?@.price < 10].title

Table 20 builds on Table 1 by comparing similar XPath concepts side by side ๐Ÿ“Š

   +==========+==================+===================================+
   | XPath    | JSONPath         | Description                       |
   +==========+==================+===================================+
   | /        | $                | the root XML element              |
   +----------+------------------+-----------------------------------+
   | .        | @                | the current XML element           |
   +----------+------------------+-----------------------------------+
   | /        | . or []          | child operator                    |
   +----------+------------------+-----------------------------------+
   | ..       | n/a              | parent operator                   |
   +----------+------------------+-----------------------------------+
   | //       | ..name,          | descendants (JSONPath borrows     |
   |          | ..[index], ..*,  | this syntax from E4X)             |
   |          | or ..[*]         |                                   |
   +----------+------------------+-----------------------------------+
   | *        | *                | wildcard: All XML elements        |
   |          |                  | regardless of their names         |
   +----------+------------------+-----------------------------------+
   | @        | n/a              | attribute access: JSON values do  |
   |          |                  | not have attributes               |
   +----------+------------------+-----------------------------------+
   | []       | []               | subscript operator used to        |
   |          |                  | iterate over XML element          |
   |          |                  | collections and for predicates    |
   +----------+------------------+-----------------------------------+
   | |        | [,]              | Union operator (results in a      |
   |          |                  | combination of node sets); called |
   |          |                  | list operator in JSONPath, allows |
   |          |                  | combining member names, array     |
   |          |                  | indices, and slices               |
   +----------+------------------+-----------------------------------+
   | n/a      | [start:end:step] | array slice operator borrowed     |
   |          |                  | from ES4                          |
   +----------+------------------+-----------------------------------+
   | []       | ?                | applies a filter (script)         |
   |          |                  | expression                        |
   +----------+------------------+-----------------------------------+
   | seamless | n/a              | expression engine                 |
   +----------+------------------+-----------------------------------+
   | ()       | n/a              | grouping                          |
   +----------+------------------+-----------------------------------+

                Table 20: XPath Syntax Compared to JSONPath

Table 21 shows some XPath expressions next to their JSONPath equivalents ๐Ÿง

   +=======================+========================+==================+
   | XPath                 | JSONPath               | Result           |
   +=======================+========================+==================+
   | /store/book/author    | $.store.book[*].author | the authors      |
   |                       |                        | of all books     |
   |                       |                        | in the store     |
   +-----------------------+------------------------+------------------+
   | //author              | $..author              | all authors      |
   +-----------------------+------------------------+------------------+
   | /store/*              | $.store.*              | all things in    |
   |                       |                        | store, which     |
   |                       |                        | are some         |
   |                       |                        | books and a      |
   |                       |                        | red bicycle      |
   +-----------------------+------------------------+------------------+
   | /store//price         | $.store..price         | the prices of    |
   |                       |                        | everything in    |
   |                       |                        | the store        |
   +-----------------------+------------------------+------------------+
   | //book[3]             | $..book[2]             | the third        |
   |                       |                        | book             |
   +-----------------------+------------------------+------------------+
   | //book[last()]        | $..book[-1]            | the last book    |
   |                       |                        | in order         |
   +-----------------------+------------------------+------------------+
   | //book[position()<3]  | $..book[0,1]           | the first two    |
   |                       | $..book[:2]            | books            |
   +-----------------------+------------------------+------------------+
   | //book[isbn]          | $..book[?@.isbn]       | filter all       |
   |                       |                        | books with an    |
   |                       |                        | ISBN number      |
   +-----------------------+------------------------+------------------+
   | //book[price<10]      | $..book[?@.price<10]   | filter all       |
   |                       |                        | books cheaper    |
   |                       |                        | than 10          |
   +-----------------------+------------------------+------------------+
   | //*                   | $..*                   | all elements     |
   |                       |                        | in an XML        |
   |                       |                        | document; all    |
   |                       |                        | member values    |
   |                       |                        | and array        |
   |                       |                        | elements         |
   |                       |                        | contained in     |
   |                       |                        | input value      |
   +-----------------------+------------------------+------------------+

     Table 21: Example XPath Expressions and Their JSONPath Equivalents

XPath can do a lot more than this comparison lists, including location paths in unabbreviated syntax, operators, and functions. There are also big differences in how the subscript operator works in XPath versus JSONPath, so let's look at those ๐Ÿ‘€

  • Square brackets in XPath expressions always operate on the _node set_ produced by the previous path fragment. Indices always start at 1.
  • With JSONPath, square brackets operate on each of the nodes in the _nodelist_ produced by the previous query segment. Array indices always start at 0 ๐Ÿ“

Appendix C. JSON Pointer ๐ŸŒˆ

This appendix is informative.

JSON Pointer [RFC6901] and JSONPath are not rivals. JSONPath is meant to be a more powerful companion to JSON Pointer, not a replacement, because they're built for different jobs ๐Ÿซถ

JSON Pointer is for identifying one single value inside a JSON value whose structure is already known.

JSONPath can identify a single value inside a JSON value too, for example by using a Normalized Path. But JSONPath is also a query syntax for searching and extracting multiple values from JSON values whose structure is only known in a general way ๐Ÿ”

A Normalized JSONPath can be converted into a JSON Pointer just by converting the syntax, without knowing anything about the JSON value. The reverse isn't generally true: a numeric reference token (path component) in a JSON Pointer might identify either a member value of an object or an element of an array. To convert it into a JSONPath query, you need to know the structure of the JSON value so you can tell those cases apart ๐Ÿ’€

Acknowledgements ๐Ÿ’œ

This document is based on Stefan Gรถssner's original online article defining JSONPath [JSONPath-orig].

The books example was taken from course material that Bielefeld University, Germany used in 2002.

This work is indebted to Christoph Burgmer for the superb JSONPath comparison project [COMPARISON], which details how over forty JSONPath implementations behave when applied to numerous queries. Iconic work, honestly ๐Ÿ‘‘

Contributors ๐Ÿฆ‹

   Marko Mikulicic
   InfluxData, Inc.
   Pisa
   Italy
   Email: mmikulicic@gmail.com


   Edward Surov
   TheSoul Publishing Ltd.
   Limassol
   Cyprus
   Email: esurov.tsp@gmail.com


   Greg Dennis
   Auckland
   New Zealand
   Email: gregsdennis@yahoo.com
   URI:   https://github.com/gregsdennis

Authors' Addresses โœจ

okay bestie, here are the editors' contact cards ๐Ÿ’Œ these are the people who wrangled this whole spec into shape, so if you ever need to reach them, this is where to find them ๐Ÿซถ

Stefan Gรถssner is the first editor ๐ŸŒธ

Stefan Gรถssner (editor)
Fachhochschule Dortmund
SonnenstraรŸe 96
D-44139 Dortmund
Germany
Email: stefan.goessner@fh-dortmund.de

Next up is Glyn Normington, the second editor ๐Ÿฆ‹

Glyn Normington (editor)
Winchester
United Kingdom
Email: glyn.normington@gmail.com

And last but definitely not least, Carsten Bormann, the third editor, with a phone number too ๐Ÿ“ž he's giving very reachable energy ๐Ÿ’…

Carsten Bormann (editor)
Universitรคt Bremen TZI
Postfach 330440
D-28359 Bremen
Germany
Phone: +49-421-218-63921
Email: cabo@tzi.org

that's the whole crew, periodt ๐Ÿ‘‘โœจ

๐Ÿ“ฆ where this page comes from