RFC 9535
JSONPath: Query Expressions for JSON
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 Notice ๐
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:
- 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]๐ข - 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
\uescapes (as specified in the rules referenced byhexchar) can be lowercase or uppercase. Theuin\uhas 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
01or-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:
- 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.
- 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:
- 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:
- 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:
- ValueType (string)
- 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:
- ValueType (string)
- 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:
- 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 before1and2,[5, 3, [{"j": 4}, {"k": 6}]]must appear before5,3, and[{"j": 4}, {"k": 6}],5must appear before3, which must appear before[{"j": 4}, {"k": 6}],5and3must appear before{"j": 4},4,{"k": 6}, and6,[{"j": 4}, {"k": 6}]must appear before{"j": 4}and{"k": 6},{"j": 4}must appear before{"k": 6},{"k": 6}must appear before4, and4must appear before6.
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
- Deprecated alias names for this type:
- 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
- [RFC0020] Cerf, V., "ASCII format for network interchange", STD 80, RFC 20, DOI 10.17487/RFC0020, October 1969,
https://www.rfc-editor.org/info/rfc20. - [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, DOI 10.17487/RFC2119, March 1997,
https://www.rfc-editor.org/info/rfc2119. - [RFC3629] Yergeau, F., "UTF-8, a transformation format of ISO 10646", STD 63, RFC 3629, DOI 10.17487/RFC3629, November 2003,
https://www.rfc-editor.org/info/rfc3629. - [RFC5234] Crocker, D., Ed. and P. Overell, "Augmented BNF for Syntax Specifications: ABNF", STD 68, RFC 5234, DOI 10.17487/RFC5234, January 2008,
https://www.rfc-editor.org/info/rfc5234. - [RFC6838] Freed, N., Klensin, J., and T. Hansen, "Media Type Specifications and Registration Procedures", BCP 13, RFC 6838, DOI 10.17487/RFC6838, January 2013,
https://www.rfc-editor.org/info/rfc6838. - [RFC7493] Bray, T., Ed., "The I-JSON Message Format", RFC 7493, DOI 10.17487/RFC7493, March 2015,
https://www.rfc-editor.org/info/rfc7493. - [RFC8126] Cotton, M., Leiba, B., and T. Narten, "Guidelines for Writing an IANA Considerations Section in RFCs", BCP 26, RFC 8126, DOI 10.17487/RFC8126, June 2017,
https://www.rfc-editor.org/info/rfc8126. - [RFC8174] Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174, May 2017,
https://www.rfc-editor.org/info/rfc8174. - [RFC8259] Bray, T., Ed., "The JavaScript Object Notation (JSON) Data Interchange Format", STD 90, RFC 8259, DOI 10.17487/RFC8259, December 2017,
https://www.rfc-editor.org/info/rfc8259. - [RFC9485] Bormann, C. and T. Bray, "I-Regexp: An Interoperable Regular Expression Format", RFC 9485, DOI 10.17487/RFC9485, October 2023,
https://www.rfc-editor.org/info/rfc9485. - [UNICODE] The Unicode Consortium, "The Unicodeยฎ Standard",
https://www.unicode.org/versions/latest/. At the time of writing,https://www.unicode.org/versions/Unicode15.0.0/UnicodeStandard-15.0.pdf.
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 ๐
- [BOOLEAN-LAWS] "Boolean algebra: Laws", December 2023,
https://en.wikipedia.org/w/index.php?title=Boolean_algebra&oldid=1191386550#Laws. - [COMPARISON] Burgmer, C., "JSONPath Comparison",
https://cburgmer.github.io/json-path-comparison/. - [E4X] ISO, "Information technology - ECMAScript for XML (E4X) specification", Withdrawn, ISO/IEC 22537:2006, February 2006,
https://www.iso.org/standard/41002.html. An equivalent specification, also withdrawn, is available fromhttps://ecma-international.org/publications-and-standards/standards/ecma-357. - [ECMA-262] ECMA International, "ECMAScript Language Specification", Standard ECMA-262, Third Edition, December 1999,
https://www.ecma-international.org/wp-content/uploads/ECMA-262_3rd_edition_december_1999.pdf. - [JSONPath-orig] Gรถssner, S., "JSONPath - XPath for JSON", February 2007,
https://goessner.net/articles/JsonPath/. - [RFC6901] Bryan, P., Ed., Zyp, K., and M. Nottingham, Ed., "JavaScript Object Notation (JSON) Pointer", RFC 6901, DOI 10.17487/RFC6901, April 2013,
https://www.rfc-editor.org/info/rfc6901. - [RFC8949] Bormann, C. and P. Hoffman, "Concise Binary Object Representation (CBOR)", STD 94, RFC 8949, DOI 10.17487/RFC8949, December 2020,
https://www.rfc-editor.org/info/rfc8949. - [SLICE] "Slice notation", commit 82f95b4, July 2022,
https://github.com/tc39/proposal-slice-notation. - [XPath] Berglund, A., Ed., Chamberlin, D., Ed., Simeon, J., Ed., Robie, J., Ed., Fernandez, M., Ed., Kay, M., Ed., and S. Boag, Ed., "XML Path Language (XPath) 2.0 (Second Edition)", W3C REC-xpath20-20101214, 14 December 2010,
https://www.w3.org/TR/2010/REC-xpath20-20101214/.
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
- ietf: rfc9535 2024-02
- ๐ girlified by claude-haiku-5-5 on : a language model's rewrite of the original, under its terms