Class JsonPathEvaluator

java.lang.Object
ca.marcusdunn.jsonlens.path.evaluator.JsonPathEvaluator

public final class JsonPathEvaluator extends Object

Applies a JsonPathQuery to a JSON value (RFC 9535).

The evaluator reads the JSON value through a JsonModel. It does not copy the value. The result is a nodelist: each Node has the caller's own node instance and its Normalized Path.

For a well-formed JsonModel and non-null arguments, the evaluator never throws an exception. It never calls a model method with a null argument, and it calls a method that is specific to a kind only for a node of that kind.

An evaluator is immutable and safe for use by more than one thread. Keep one instance and use it again.

Evaluate a query

JsonPathQuery query = query("$..book[?@.isbn].author");
List<Node<JsonNode>> nodes = JsonPathEvaluator.standard()
        .evaluate(query, root, JacksonJsonModel.INSTANCE)
        .orElse(List.of());

Node<JsonNode> first = nodes.getFirst();
JsonNode value = first.value();          // the Jackson node "Herman Melville" of the tree
NormalizedPath path = first.path();      // $['store']['book'][2]['author']

The result nodes are in the order that RFC 9535 specifies. Array elements are in array order. Object members are in the order of JsonModel.memberCursor(Object).

Filters and functions

// Comparisons, logical operators, and the standard functions.
List<String> queries = List.of(
        "$.store.book[?@.price < 10 && @.category == 'fiction'].title", // ["Moby Dick"]
        "$.store.book[?search(@.author, 'Re*s')].author",               // ["Nigel Rees"]
        "$.store.book[?match(@.title, '.*Ring.*')].price",              // [22.99]
        "$.store[?length(@.color) == 3].color",                         // ["red"]
        "$.store.book[?count(@.*) == 5].title",                         // the books with an isbn
        "$.store.book[-1:].title");                                     // ["The Lord of the Rings"]
for (String text : queries) {
    List<Node<JsonNode>> nodes = evaluator.evaluate(query(text), root, JacksonJsonModel.INSTANCE).orElse(List.of());
    System.out.println(text + " -> " + nodes.stream().map(Node::value).toList());
}

Errors

A query from the parser is valid, so for such a query only the overflow indications of JsonPathEvaluator.Limits can occur. A query that a caller makes directly can also give a validity error:

// A query that is made directly, not by the parser, can be invalid.
JsonPathQuery bad = new JsonPathQuery(List.of(new ca.marcusdunn.jsonlens.path.core.query.Segment.Child(
        List.of(new ca.marcusdunn.jsonlens.path.core.query.Selector.Index(1L << 60)))));
Result<List<Node<JsonNode>>, EvaluationError> result =
        JsonPathEvaluator.standard().evaluate(bad, MAPPER.readTree("[]"), JacksonJsonModel.INSTANCE);
// result: Err(IntegerOutOfRange[value=1152921504606846976])

Function extensions

An evaluator from withFunctions(List) also has the read-only function extensions that you give. It still accepts each JsonModel. See FunctionExtension.

An extension that makes new JSON values is a BuildingFunctionExtension. Register it with withFunctions(List, List), which gives a BuildingEvaluator. A building evaluator accepts only a model that also implements JsonFactory.

  • Field Details

    • MAX_QUERY_DEPTH

      public static final int MAX_QUERY_DEPTH
      The maximum number of nested expressions, filters, and function calls in a query. A query from the parser is always below this limit. A deeper query gives EvaluationError.QueryTooDeep.
      See Also:
  • Method Details

    • standard

      public static JsonPathEvaluator standard()
      Returns an evaluator with the standard functions (length, count, match, search, and value) and the default limits.
      Returns:
      an evaluator with the standard functions
    • withFunctions

      public static Result<JsonPathEvaluator, ExtensionError> withFunctions(List<FunctionExtension> extensions)

      Returns an evaluator with the standard functions and more function extensions.

      The parser that makes the queries must know the same signatures:

      // 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]
      
      Parameters:
      extensions - the function extensions
      Returns:
      the evaluator, or an error if a name is not valid or is used more than once, or if a read-only extension has a ValueType parameter
    • withFunctions

      public static Result<BuildingEvaluator, ExtensionError> withFunctions(List<FunctionExtension> readOnly, List<BuildingFunctionExtension> building)

      Returns an evaluator with the standard functions, read-only function extensions, and building function extensions.

      A building extension can make new JSON values, so the evaluator accepts only models that also implement JsonFactory. See BuildingFunctionExtension.

      Parameters:
      readOnly - the read-only function extensions
      building - the building function extensions
      Returns:
      the evaluator, or an error if a name is not valid or is used more than once, or if a read-only extension has a ValueType parameter
    • withLimits

      public JsonPathEvaluator withLimits(JsonPathEvaluator.Limits limits)
      Returns an evaluator with the same functions and other limits.
      Parameters:
      limits - the limits
      Returns:
      the evaluator
    • limits

      public JsonPathEvaluator.Limits limits()
      Returns the limits of this evaluator.
      Returns:
      the limits
    • functions

      public List<FunctionSignature> functions()
      Returns the signatures of the functions that this evaluator has.
      Returns:
      the signatures, the standard functions first
    • evaluate

      public <N> Result<List<Node<N>>, EvaluationError> evaluate(JsonPathQuery query, N root, JsonModel<N> model)

      Applies a query to a JSON value.

      The evaluator first checks the validity rules that the Java types of a query cannot express: the I-JSON range of integers, the signatures of function calls, and the nesting depth. A query from the parser always obeys these rules. Then the evaluator applies the segments of the query, one after the other, to the root node.

      JsonPathQuery query = query("$..book[?@.isbn].author");
      List<Node<JsonNode>> nodes = JsonPathEvaluator.standard()
              .evaluate(query, root, JacksonJsonModel.INSTANCE)
              .orElse(List.of());
      
      Node<JsonNode> first = nodes.getFirst();
      JsonNode value = first.value();          // the Jackson node "Herman Melville" of the tree
      NormalizedPath path = first.path();      // $['store']['book'][2]['author']
      
      Result When
      Ok with an empty list the query selects nothing. This is not an error.
      Ok with nodes the query selects nodes
      Err with EvaluationError.NodelistTooLarge or EvaluationError.RegexTooComplex a limit of JsonPathEvaluator.Limits is passed
      Err with another EvaluationError the query is not valid, or a function extension does not obey its signature
      Type Parameters:
      N - the node type
      Parameters:
      query - the query
      root - the query argument: the root node value
      model - the model for the node type
      Returns:
      the nodelist, or an error