Skip to content

JMESPath Query Evaluation

recipe JSON Schema C# jmespath query evaluation type required items properties format

This recipe demonstrates how to evaluate JMESPath expressions against JSON data using the Corvus.Text.Json.JMESPath library. It covers basic identifiers, sub-expressions, index expressions, projections (list, object, flatten, filter), slicing, pipe expressions, multiselect (lists and hashes), built-in functions, and build-time expression compilation via the source generator.

The Pattern

JMESPath is a query language for JSON that lets you extract and transform elements from a JSON document. Expressions are compiled to delegate trees on first use and cached. The evaluator is thread-safe and designed for high-throughput, zero-allocation evaluation.

The Corvus implementation passes all tests in the official JMESPath compliance test suite.

The Schema

File: order.json

{
    "title": "Order",
    "type": "object",
    "required": ["customer", "items"],
    "properties": {
        "customer": { "type": "string" },
        "items": {
            "type": "array",
            "items": {
                "type": "object",
                "required": ["name", "price", "quantity"],
                "properties": {
                    "name": { "type": "string" },
                    "price": { "type": "number" },
                    "quantity": { "type": "integer", "format": "int32" }
                }
            }
        }
    }
}

Evaluating Expressions

Search — the core API

JMESPath expressions are evaluated with JMESPathEvaluator.Search():

var evaluator = JMESPathEvaluator.Default;

// Simple search — result is cloned and safe to use
JsonElement customer = evaluator.Search("customer", dataDoc.RootElement);
// "Alice"

// With a workspace for zero-allocation evaluation
using JsonWorkspace workspace = JsonWorkspace.Create();
JsonElement result = evaluator.Search("items[0].name", dataDoc.RootElement, workspace);
// "Widget"

Basic Expressions

// Identifier — select a key
evaluator.Search("customer", data);
// "Alice"

// Sub-expression — nested navigation
evaluator.Search("a.b.c.d", data);

// Index expression — select by position (0-based)
evaluator.Search("items[0].name", data);
// "Widget"

// Negative index — from end of array
evaluator.Search("items[-1].name", data);
// "Doohickey"

Projections

List projections ([*])

Apply an expression to each element in an array. null results are omitted.

evaluator.Search("people[*].first", peopleData);
// ["James", "Jacob", "Jayden"]

Object projections (*)

Apply an expression to each value in an object:

evaluator.Search("ops.*.numArgs", opsData);
// [2, 3]

Flatten projections ([])

Flatten nested arrays into a single list:

evaluator.Search("reservations[].instances[].state", reservationsData);
// ["running", "stopped", "terminated", "running"]

Filter projections ([? ... ])

Filter array elements by a predicate:

evaluator.Search("machines[?state=='running'].name", machinesData);
// ["a", "c"]

Slicing

Select contiguous subsets of an array using [start:stop:step]:

evaluator.Search("[0:5]", numbersData);   // [0, 1, 2, 3, 4]
evaluator.Search("[::2]", numbersData);   // [0, 2, 4, 6, 8]
evaluator.Search("[::-1]", numbersData);  // [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

Pipe Expressions

Stop a projection and operate on its result:

evaluator.Search("people[*].first | [0]", peopleData);
// "James"

Without the pipe, people[*].first[0] would try to index each string (which is not valid), returning an empty array. The pipe collects the projection result first, then applies [0] to the collected array.

MultiSelect

MultiSelect lists

Create arrays from selected values:

evaluator.Search("people[*].[name, state.name]", data);
// [["James", "Virginia"], ["Jacob", "Texas"], ["Jayden", "Florida"]]

MultiSelect hashes

Create objects with renamed keys:

evaluator.Search("people[*].{Name: name, State: state.name}", data);
// [{"Name": "James", "State": "Virginia"}, ...]

Built-in Functions

JMESPath provides functions for aggregation, type checking, string operations, and more:

Category Functions
Aggregation sum, avg, min, max, length
String starts_with, ends_with, contains, join, reverse
Type type, to_number, to_string, to_array, not_null
Math abs, ceil, floor
Sorting sort, sort_by, min_by, max_by
Object keys, values, merge
Higher-order map
evaluator.Search("length(items)", data);                  // 3
evaluator.Search("sum(items[*].price)", data);             // 129.49
evaluator.Search("max_by(items, &price).name", data);      // "Gadget"
evaluator.Search("sort_by(items, &price)[*].name", data);  // ["Doohickey", "Widget", "Gadget"]

// Filter with contains
evaluator.Search("myarray[?contains(@, 'foo')]", data);
// ["foo", "foobar", "barfoo", "barfoobaz"]

Note the & prefix in sort_by and max_by — this creates an expression reference (similar to a lambda) that the function evaluates against each array element.

Source-Generated Expressions

For maximum performance, compile expressions at build time using the [JMESPathExpression] attribute. Add the expression file as an AdditionalFiles item and reference the source generator:

File: total-price.jmespath

sum(items[*].price)
[JMESPathExpression("total-price.jmespath")]
public static partial class TotalPrice;

The generator compiles the expression to optimized C# at build time:

using JsonWorkspace workspace = JsonWorkspace.Create();
JsonElement result = TotalPrice.Evaluate(dataDoc.RootElement, workspace);
// 129.49

Running the Example

cd docs/ExampleRecipes/026-JMESPath
dotnet run
  • 025-JSONata — JSONata expression evaluation (a more expressive query language with object construction, variable bindings, and higher-order functions)
  • 024-JsonLogic — JsonLogic rule evaluation (a declarative rules engine)
  • 023-JsonPatch — RFC 6902 JSON Patch operations