Crate serde_json_path

source ·
Expand description

This crate allows you to use JSONPath queries to extract nodelists from a serde_json::Value.

The crate intends to adhere to the IETF JSONPath standard (RFC 9535). Check out the specification to read more about JSONPath query syntax and to find many examples of its usage.

§Features

This crate provides three key abstractions:

In addition, the JsonPathExt trait is provided, which extends the serde_json::Value type with the json_path method for performing JSONPath queries.

Finally, the #[function] attribute macro can be used to extend JSONPath queries to use custom functions.

§Usage

§Parsing

JSONPath query strings can be parsed using the JsonPath type:

use serde_json_path::JsonPath;

let path = JsonPath::parse("$.foo.bar")?;

You then have two options to query a serde_json::Value using the parsed JSONPath: JsonPath::query or JsonPath::query_located. The former will produce a NodeList, while the latter will produce a LocatedNodeList. The two options provide similar functionality, but it is recommended to use the former unless you have need of node locations in the query results.

§Querying for single nodes

For queries that are expected to return a single node, use either the exactly_one or the at_most_one method.

use serde_json::json;

let value = json!({ "foo": { "bar": ["baz", 42] } });
let path = JsonPath::parse("$.foo.bar[0]")?;
let node = path.query(&value).exactly_one()?;
assert_eq!(node, "baz");

JSONPath allows access via reverse indices:

let value = json!([1, 2, 3, 4, 5]);
let path = JsonPath::parse("$[-1]")?;
let node = path.query(&value).at_most_one()?;
assert_eq!(node, Some(&json!(5)));

Keep in mind, that for simple queries, the serde_json::Value::pointer method may suffice.

§Querying for multiple nodes

For queries that are expected to return zero or many nodes, use the all method. There are several selectors in JSONPath whose combination can produce useful and powerful queries.

§Wildcards (*)

Wildcards select everything under a current node. They work on both arrays, by selecting all array elements, and on objects, by selecting all object key values:

let value = json!({ "foo": { "bar": ["baz", "bop"] } });
let path = JsonPath::parse("$.foo.bar[*]")?;
let nodes = path.query(&value).all();
assert_eq!(nodes, vec!["baz", "bop"]);
§Slice selectors (start:end:step)

Extract slices from JSON arrays using optional start, end, and step values. Reverse indices can be used for start and end, and a negative step can be used to traverse the array in reverse order. Consider the following JSON object, and subsequent examples:

let value = json!({ "foo": [1, 2, 3, 4, 5] });

start, end, and step are all optional:

let path = JsonPath::parse("$.foo[:]")?;
let nodes = path.query(&value).all();
assert_eq!(nodes, vec![1, 2, 3, 4, 5]);

Omitting end will go to end of slice:

let path = JsonPath::parse("$.foo[2:]")?;
let nodes = path.query(&value).all();
assert_eq!(nodes, vec![3, 4, 5]);

Omitting start will start from beginning of slice:

let path = JsonPath::parse("$.foo[:2]")?;
let nodes = path.query(&value).all();
assert_eq!(nodes, vec![1, 2]);

You can specify the step size:

let path = JsonPath::parse("$.foo[::2]")?;
let nodes = path.query(&value).all();
assert_eq!(nodes, vec![1, 3, 5]);

Or use a negative step to go in reverse:

let path = JsonPath::parse("$.foo[::-1]")?;
let nodes = path.query(&value).all();
assert_eq!(nodes, vec![5, 4, 3, 2, 1]);

Finally, reverse indices can be used for start or end:

let path = JsonPath::parse("$.foo[-2:]")?;
let nodes = path.query(&value).all();
assert_eq!(nodes, vec![4, 5]);
§Filter expressions (?)

Filter selectors allow you to use logical expressions to evaluate which members in a JSON object or array will be selected. You can use the boolean && and || operators as well as parentheses to group logical expressions in your filters. The current node (@) operator allows you to utilize the node being filtered in your filter logic:

let value = json!({ "foo": [1, 2, 3, 4, 5] });
let path = JsonPath::parse("$.foo[?@ > 2 && @ < 5]")?;
let nodes = path.query(&value).all();
assert_eq!(nodes, vec![3, 4]);

You can form relative paths on the current node, as well as absolute paths on the root ($) node when writing filters:

let value = json!({
    "threshold": 40,
    "readings": [
        { "val": 35, "msg": "foo" },
        { "val": 40, "msg": "bar" },
        { "val": 42, "msg": "biz" },
        { "val": 48, "msg": "bop" },
    ]
});
let path = JsonPath::parse("$.readings[? @.val > $.threshold ].msg")?;
let nodes = path.query(&value).all();
assert_eq!(nodes, vec!["biz", "bop"]);

Filters also allow you to make use of functions in your queries:

let value = json!([
    "a short string",
    "a longer string",
    "an unnecessarily long string",
]);
let path = JsonPath::parse("$[? length(@) < 20 ]")?;
let nodes = path.query(&value).all();
assert_eq!(nodes, vec!["a short string", "a longer string"]);
§Descendant Operator (..)

JSONPath query segments following a descendant operator (..) will visit the input node and each of its descendants.

let value = json!({
    "foo": {
        "bar": {
            "baz": 1
        },
        "baz": 2
    },
    "baz": 3,
});
let path = JsonPath::parse("$.foo..baz")?;
let nodes = path.query(&value).all();
assert_eq!(nodes, vec![2, 1]);

§Node locations and NormalizedPath

Should you need to know the locations of the nodes produced by your queries, you can make use of the JsonPath::query_located method to perform the query. The resulting LocatedNodeList contains both the nodes produced by the query, as well as their locations represented by their NormalizedPath.

let value = json!({
    "foo": {
        "bar": {
            "baz": 1
        },
        "baz": 2
    },
    "baz": 3,
});
let path = JsonPath::parse("$..[? @.baz == 1]")?;
let location = path
    .query_located(&value)
    .exactly_one()?
    .location()
    .to_string();
assert_eq!(location, "$['foo']['bar']");

Modules§

Structs§

Enums§

Traits§

Attribute Macros§

  • Register a function for use in JSONPath queries