Class JsonPathParser
Makes a JsonPathQuery from query text (RFC 9535).
The parser checks that the query is well-formed (it agrees with the ABNF grammar) and
valid (its integers are in the I-JSON range and its function expressions are well-typed).
It reports the first problem as a ParseError. It never throws an exception for a
non-null argument.
A parser is immutable and safe for use by more than one thread. Keep one instance and use it again.
Parse a query
JsonPathParser parser = JsonPathParser.standard(); // immutable and thread-safe
Result<JsonPathQuery, ParseError> result = parser.parse("$.store.book[0].title");
String text = result.map(JsonPathQuery::toString).orElse("not valid");
// text: "$['store']['book'][0]['title']" (shorthand forms become bracket notation)
Handle errors
ParseError is a sealed interface of records. Each error has a position and a message:
String describe = switch (JsonPathParser.standard().parse("$[?length(@.*) > 3]")) {
case Result.Ok(JsonPathQuery query) -> "valid: " + query;
case Result.Err(ParseError.NonSingularQuery(int position)) ->
"@.* can select more than one node, at position " + position;
case Result.Err(ParseError.UnknownFunction(int position, String name)) ->
"no function " + name;
case Result.Err(ParseError error) -> error.message(); // all other errors
};
// describe: "@.* can select more than one node, at position 10"
ParseError error = JsonPathParser.standard().parse("$.a[01]").fold(query -> null, e -> e);
String message = error.message();
// "The integer '01' is not valid. An integer must not have a leading zero or be '-0'. Position: 4."
What the parser checks
| Rule | Example of an error | Error |
|---|---|---|
| The grammar of RFC 9535 | $.a., $[?@.a = 1], $ |
ParseError.UnexpectedCharacter, ParseError.UnexpectedEnd |
| Integers in the I-JSON range | $[9007199254740992] |
ParseError.IntegerOutOfRange |
| Integers without leading zeros | $[01], $[-0] |
ParseError.InvalidInteger |
| Only known functions | $[?foo(@)] |
ParseError.UnknownFunction |
| Well-typed function calls | $[?length(@.*) > 1], $[?count(@) == true && match(@)] |
ParseError.NonSingularQuery, ParseError.WrongArgumentCount, ParseError.ArgumentTypeMismatch, ParseError.NotComparable, ParseError.ValueTypeInTest |
| Unicode scalar values only | a string with an unpaired surrogate | ParseError.UnpairedSurrogate |
| A limit on nesting | 300 nested parentheses | ParseError.NestingTooDeep |
Function extensions
A parser from withFunctions(List) also knows the function extensions that you give. See
FunctionSignature.
-
Field Summary
FieldsModifier and TypeFieldDescriptionstatic final intThe maximum number of nested filter selectors, parentheses, and function calls. -
Method Summary
Modifier and TypeMethodDescriptionReturns the functions that this parser knows.parse(byte[] utf8) Parses query text in UTF-8 (RFC 9535, Section 2.1).Parses query text.static JsonPathParserstandard()Returns a parser that knows the standard functions: length, count, match, search, and value.withFunctions(List<FunctionSignature> extensions) Returns a parser that knows the standard functions and more function extensions.
-
Field Details
-
MAX_NESTING_DEPTH
public static final int MAX_NESTING_DEPTHThe maximum number of nested filter selectors, parentheses, and function calls.
A deeper query gives
ParseError.NestingTooDeep. The limit prevents a stack overflow from a query that an attacker makes (RFC 9535, Section 4.1).- See Also:
-
-
Method Details
-
standard
Returns a parser that knows the standard functions: length, count, match, search, and value.- Returns:
- a parser for the standard functions
-
withFunctions
public static Result<JsonPathParser, FunctionRegistrationError> withFunctions(List<FunctionSignature> extensions) Returns a parser that knows the standard functions and more function extensions.
The evaluator that applies the queries must have an implementation for each signature.
// The parser needs the signature, to check that queries are well-typed. Result<JsonPathParser, FunctionRegistrationError> parser = JsonPathParser.withFunctions(List.of(First.SIGNATURE)); // The evaluator needs the implementation. Result<JsonPathEvaluator, ExtensionError> evaluator = JsonPathEvaluator.withFunctions(List.of(new First())); JsonPathQuery query = parser.orElse(JsonPathParser.standard()) .parse("$[?first(@.*) > 4]") .orElse(null); List<Node<JsonNode>> nodes = evaluator.orElse(JsonPathEvaluator.standard()) .evaluate(query, root, JacksonJsonModel.INSTANCE) .orElse(List.of()); // nodes: the path $[1]A name that is not valid gives
FunctionRegistrationError.InvalidName. A name that is used more than once, or that is the name of a standard function, givesFunctionRegistrationError.DuplicateName:Result<JsonPathParser, FunctionRegistrationError> clash = JsonPathParser.withFunctions(List.of( new FunctionSignature("length", FunctionType.VALUE, List.of(FunctionType.VALUE)))); // clash: Err(DuplicateName[name=length]). A standard function cannot be replaced.- Parameters:
extensions- the signatures of the function extensions- Returns:
- the parser, or an error if a name is not valid or is used more than once
-
functions
Returns the functions that this parser knows.- Returns:
- the function signatures, the standard functions first
-
parse
Parses query text.
The text must be the complete query. Blank space before
$or after the last segment is not permitted. Shorthand forms become their bracket equivalents:JsonPathParser parser = JsonPathParser.standard(); // immutable and thread-safe Result<JsonPathQuery, ParseError> result = parser.parse("$.store.book[0].title"); String text = result.map(JsonPathQuery::toString).orElse("not valid"); // text: "$['store']['book'][0]['title']" (shorthand forms become bracket notation)- Parameters:
query- the query text- Returns:
- the query, or the first error
-
parse
Parses query text in UTF-8 (RFC 9535, Section 2.1).
Use this method for a query that comes from a network or a file. The decoder does not replace bytes that are not valid. It gives an error with the position of the first such byte:
byte[] utf8 = "$['café']".getBytes(StandardCharsets.UTF_8); Result<JsonPathQuery, ParseError> fromBytes = JsonPathParser.standard().parse(utf8); byte[] broken = {'$', '[', '\'', (byte) 0xC3, '(', '\'', ']'}; Result<JsonPathQuery, ParseError> rejected = JsonPathParser.standard().parse(broken); // rejected: Err(InvalidUtf8[position=3])- Parameters:
utf8- the query text in UTF-8- Returns:
- the query, or the first error. Bytes that are not well-formed UTF-8 give
ParseError.InvalidUtf8.
-