Filters
editA filter selector, [?test], keeps the children of a node for which the test is true: the elements of an array, the member values of an object. In the test, @ is the child being tested and $ is the root of the document.
Compare values
var document = JsonNode.Parse("""
{
"books": [
{ "title": "A", "price": 8, "category": "fiction" },
{ "title": "B", "price": 12, "category": "science" },
{ "title": "C", "price": 25, "category": "fiction" }
]
}
""");
string Select(string query) => JsonPath.Parse(query).Evaluate(document).ToJsonArray().ToJsonString();
// @ is the node being tested: here, each book
Assert.Equal("""["A","B"]""", Select("$.books[?@.price < 20].title"));
Assert.Equal("""["A","C"]""", Select("$.books[?@.category == 'fiction'].title"));
// && (and), || (or), ! (not) and parentheses combine the tests
Assert.Equal("""["A"]""", Select("$.books[?@.category == 'fiction' && @.price < 20].title"));
Assert.Equal("""["B"]""", Select("$.books[?!(@.category == 'fiction')].title"));
// Parentheses around the whole test are allowed, as in other JSONPath dialects
Assert.Equal("""["A","B"]""", Select("$.books[?(@.price < 20)].title"));- The comparison operators are
==,!=,<,<=,>and>=. - The literals are numbers (
10,-1.5,1e3), strings in single or double quotes,true,falseandnull. There are no array or object literals: compare with a query instead. - The logical operators are
&&,||and!.!applies to what follows it, so!@.a == 1is invalid: write!(@.a == 1).
Test whether a member exists
A query on its own, without a comparison, is true when it selects at least one node:
var document = JsonNode.Parse("""
[
{ "name": "A", "enabled": true },
{ "name": "B", "enabled": false },
{ "name": "C" }
]
""");
string Select(string query) => JsonPath.Parse(query).Evaluate(document).ToJsonArray().ToJsonString();
// A query alone tests whether the member exists, whatever its value
Assert.Equal("""["A","B"]""", Select("$[?@.enabled].name"));
Assert.Equal("""["C"]""", Select("$[?!@.enabled].name"));
// Compare with true to test the value
Assert.Equal("""["A"]""", Select("$[?@.enabled == true].name"));[?@.enabled] keeps the element whose enabled member is false: the test checks that the member exists, not that its value is true. Compare with true to test the value.
Refer to the root, or filter inside a filter
var document = JsonNode.Parse("""
{
"maxPrice": 10,
"orders": [
{ "id": 1, "lines": [{ "price": 4 }, { "price": 15 }] },
{ "id": 2, "lines": [{ "price": 9 }] }
]
}
""");
string Select(string query) => JsonPath.Parse(query).Evaluate(document).ToJsonArray().ToJsonString();
// $ refers to the root of the document from inside a filter
Assert.Equal("[4,9]", Select("$.orders[*].lines[?@.price <= $.maxPrice].price"));
// A filter inside a filter: the orders that have at least one line above 10
Assert.Equal("[1]", Select("$.orders[?@.lines[?@.price > 10]].id"));$in a test is the root of the document, so a query can compare with a value stored elsewhere in the document.- A query in a test can contain any segment, including another filter or a descendant segment (
@..price).
Filter the members of an object
On an object, the filter tests the value of each member and keeps the values that pass:
var document = JsonNode.Parse("""{"users":{"alice":{"age":31},"bob":{"age":25}}}""");
// On an object, a filter tests the value of every member
var result = JsonPath.Parse("$.users[?@.age > 30]").Evaluate(document);
Assert.Equal(["$['users']['alice']"], result.Select(match => match.Path));The result is the member value ({"age":31}), not the member name. Its path, $['users']['alice'], gives the name.
How values compare
var document = JsonNode.Parse("""
[
{ "id": 1, "value": 1 },
{ "id": 2, "value": 1.0 },
{ "id": 3, "value": "1" },
{ "id": 4, "value": [1, 2] },
{ "id": 5 }
]
""");
string Select(string query) => JsonPath.Parse(query).Evaluate(document).ToJsonArray().ToJsonString();
// Numbers compare by value; values of different types are never equal
Assert.Equal("[1,2]", Select("$[?@.value == 1].id"));
// Arrays and objects compare deeply: here, with the value of the fourth element
Assert.Equal("[4]", Select("$[?@.value == $[3].value].id"));
// A missing member is only equal to another missing member, and is not less or greater than anything
Assert.Equal("[5]", Select("$[?@.value == @.missing].id"));
Assert.Equal("[]", Select("$[?@.value < @.missing || @.value > @.missing].id"));- Values of different types are never equal: the string
"1"is not equal to the number1, andnullis only equal tonull. - Numbers compare by value:
1,1.0and1e0are equal. They are compared asdoublevalues; see Number precision. - Strings compare by Unicode code point, without culture rules:
"B" < "a"is true,"a" < "B"is false. - Arrays and objects compare deeply: equal when they have the same elements in the same order, or the same members with equal values in any order.
- For objects with duplicate member names,
JsonElementequality uses the last value for each name, as doesJsonElement.TryGetProperty; earlier values for that name are ignored. - Only numbers and strings are ordered:
<,<=,>and>=are false for Booleans,null, arrays, objects and values of different types.<=is "less than or equal", sonull <= nullis true. - A query that selects nothing compares only with another query that selects nothing: both sides are then equal, and neither is less than the other.
Rules for the queries in a test
A query compared with == or < must be a singular query: one that selects at most one node, made of names and indexes only, such as @.price, @['first name'] or $.limits[0]. A wildcard, a slice, a filter or .. makes it non-singular, and Parse rejects the comparison. To compare one value selected by such a query, use value().
Inside the brackets of a singular query, write no blank space: @['a'] == 1 is valid, @[ 'a' ] == 1 is not.