jsonpath-rust
repository·main·Indexed 41 days ago
https://github.com/besok/jsonpath-rustA high-performance Rust implementation of the JsonPath query language, fully compliant with the RFC 9535 specification. It provides functionality to query JSON data using paths, values, and custom filter extensions like in, nin, any_of, none_of, and subset_of. The library includes the Queryable trait for retrieving matched values and paths, and supports parsing JSONPath strings into a JpQuery AST with built-in nesting depth protection.
What's inside jsonpath-rust
- jsonpath-rust is a Rust library for querying JSON data using the JsonPath query language, inspired by XPath for XML. It is designed to be fully compliant with the RFC 9535 specification.
Install Python bindings
mainPython bindings for
jsonpath-rustare available via PyPI under the package namejsonpath-rust-bindings.pip install jsonpath-rust-bindingsRun RFC 9535 compliance tests
mainTo verify the RFC 9535 implementation, run the
main.rsfile. The test runner will output results to the console and save a detailed report toresults.csv.Console Output Format:
- Skips are indicated as:
Skipping test case: <name> because of reason: <reason> - Failures are grouped under
Failed tests:with the test name and reason. - A summary section provides the total count of tests, passed, failed, and skipped cases.
# Run the main test entry point # (Assuming cargo or rustc is configured to run main.rs) rustc rfc9535/src/main.rs && ./main- Skips are indicated as:
Migrating to jsonpath-rust v1.0.0+
mainVersion 1.0.0 introduced breaking changes to achieve RFC 9535 compliance. If you are upgrading from older versions, note the following mapping:
Old Concept New Concept JsonLikeQueryableResult<Value, JsonPathParserError>Queried<Queryable>JsonPath#{query_with_path, query_only_path, query}Operates on QueryableJsonPathParserErrorJsonPathErrorin,nin,anyOf,noneOf,subsetOfin,nin,any_of,none_of,subset_of(renamed to snake_case)Note: The
lengthfunction was removed. Use Rust's native length expressions within filters instead.Implement the Queryable trait for custom data structures
mainThe
Queryabletrait abstracts JSON-like data structures, allowing them to be used with the JSONPath query engine. To use your own data type with JSONPath, you must implementQueryableand satisfy several trait bounds includingDefault,Clone,Debug, and conversions from&str,bool,i64,f64,Vec<Self>, andString.use jsonpath_rust::JsonPath; use serde_json::json; let data = json!({ "store": { "books": [ {"title": "Book 1", "price": 10}, {"title": "Book 2", "price": 15} ] } }); // Access data using the Queryable trait let books = data.query("$.store.books[*].title").expect("no errors");Nesting depth limits in JSONPath parsing
mainTo protect against stack overflow during recursive parsing,
jsonpath-rustenforces a maximum nesting depth for parentheses()and brackets[].- The limit is set by
MAX_NESTING_DEPTH(currently128). - The depth check is performed by scanning the raw input string before the full parse tree is built.
- Brackets and parentheses inside string literals are ignored by the depth checker and do not count towards the limit.
- The limit is set by
Modify JSON data using paths
mainThe library allows for targeted updates to JSON structures. While the specific
reference_mutanddelete_by_pathmethods are part of the broader API (often accessed via traits onQueryabletypes), the pattern involves:- Finding the path of the element to modify using
query_only_path. - Using the resulting path to obtain a mutable reference or perform a deletion.
// Conceptual pattern for updating let mut json = serde_json::json!([{"verb": "RUN", "distance": [1]}]); let path_elements = json.query_only_path("$[?(@.verb == 'RUN')]")?; if let Some(path) = path_elements.first() { // Use the path to mutate the underlying structure // (Implementation details depend on the specific Queryable implementation) }- Finding the path of the element to modify using
Filter RFC 9535 test cases
mainYou can exclude specific test cases from the compliance suite by using the
filtered_cases.jsonfile. This file must be a JSON array containing objects that specify which test case names to filter out and the reason for doing so.[ { "name": "test_case_name", "reason": "reason_for_skipping" } ]Perform high-performance queries with `js_path_process`
mainIf you are running the same JSONPath query multiple times against different data structures, you should avoid parsing the path string repeatedly. Instead, parse the path once into a
JpQueryobject and usejs_path_processto execute it.- Parse the path using
parse_json_path(path)to get aJpQuery. - Call
js_path_process(&query, &value)for each data object.
use crate::parser::parse_json_path; let query = parse_json_path("$.store.book[*].author")?; let json1 = serde_json::json!({ "store": { "book": [ {"author": "A"}, {"author": "B"} ] } }); let json2 = serde_json::json!({ "store": { "book": [ {"author": "C"}, {"author": "D"} ] } }); let res1 = js_path_process(&query, &json1)?; let res2 = js_path_process(&query, &json2)?;- Parse the path using
Use JsonPath extensions for array filtering
mainThe library provides several custom filter extensions to check relationships between elements and arrays:
in: Checks if the first argument is in the array provided as the second argument. Example:$.elems[?in(@, $.list)]nin: Checks if the first argument is not in the array provided as the second argument. Example:$.elems[?nin(@, $.list)]none_of: Checks if none of the elements in the first array are in the second array. Example:$.elems[?none_of(@, $.list)]any_of: Checks if any of the elements in the first array are in the second array. Example:$.elems[?any_of(@, $.list)]subset_of: Checks if all elements in the first array are in the second array. Example:$.elems[?subset_of(@, $.list)]
Update JSON structure by path
mainThe library does not support updating JSON directly within a query string. Instead, you must find the path using a query and then use the
Queryabletrait's reference methods to mutate the structure manually.Supported path elements for direct access:
- root
- field
- index
Methods:
reference_mut: Returns anOption<&mut Self>for a given path.reference: Returns anOption<&Self>for a given path.
Example of updating an array element:
let mut json = json!([{"verb": "RUN", "distance": [1]}]...); let path = json.query_only_path("$[?(@.verb == 'RUN')]")?; let elem = path.first().unwrap_or_default(); if let Some(v) = json.reference_mut(elem)... { // mutate v }fn update_by_path_test() -> Queried<()> { let mut json = json!([ {"verb": "RUN","distance":[1]}, {"verb": "TEST"}, {"verb": "DO NOT RUN"} ]); let path = json.query_only_path("$[?(@.verb == 'RUN')]")?; let elem = path.first().unwrap_or_default(); if let Some(v) = json .reference_mut(elem) .and_then(|v| v.as_object_mut()) .and_then(|v| v.get_mut("distance")) .and_then(|v| v.as_array_mut()) { v.push(json!(2)) } assert_eq!( json, json!([ {"verb": "RUN","distance":[1,2]}, {"verb": "TEST"}, {"verb": "DO NOT RUN"} ]) ); Ok() }Query JSON data with paths and values
mainThe
Queryabletrait provides different methods depending on whether you need the values, the paths, or both.Queried with path
Returns a collection of
QueryRef<Value>, which is a tuple of(value, path). This is useful when you need to know exactly where a match was found.let vec: Vec<QueryRef<Value>> = json.query_with_path("$[1,5:7]")?;Queried without path
Returns a collection of references to the values (
&Value).let vec: Vec<&Value> = json.query("$[?@.a==1E2]")?;Queried with only path
Returns a collection of the path strings (
String).let vec: Vec<String> = json.query_only_path("$[?@<3]")?;fn union() -> Queried<()> { let json = json!([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); // QueryRes is a tuple of (value, path) for references and just value for owned values let vec: Vec<QueryRef<Value>> = json.query_with_path("$[1,5:7]")?; assert_eq!( vec, vec![ (&json!(1), "$[1]".to_string()).into(), (&json!(5), "$[5]".to_string()).into(), (&json!(6), "$[6]".to_string()).into(), ] ); Ok(()) } fn exp_no_error() -> Queried<()> { let json = json!([ { "a": 100, "d": "e" }, { "a": 100.1, "d": "f" }, { "a": "100", "d": "g" } ]); let vec: Vec<&Value> = json.query("$[?@.a==1E2]")?; assert_eq!( vec.iter().collect::<Vec<_>>(), vec![&json!({"a":100, "d":"e"})] ); Ok(()) } fn filter_data() -> Queried<()> { let json = json!({ "a": 1, "b": 2, "c": 3 }); let vec: Vec<String> = json .query_only_path("$[?@<3]")? .into_iter() .map(Option::unwrap_or_default) .collect(); assert_eq!(vec, vec!["$['a']".to_string(), "$['b']".to_string()]); Ok() }