jsonpath-rust

repository·main·Indexed 41 days ago

https://github.com/besok/jsonpath-rust

A 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.

Tokens
6.7K
Snippets
21
Records
30
Agent score
51%

What's inside jsonpath-rust

  1. Run RFC 9535 compliance tests

    main

    To verify the RFC 9535 implementation, run the main.rs file. The test runner will output results to the console and save a detailed report to results.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
  2. Migrating to jsonpath-rust v1.0.0+

    main

    Version 1.0.0 introduced breaking changes to achieve RFC 9535 compliance. If you are upgrading from older versions, note the following mapping:

    Old ConceptNew Concept
    JsonLikeQueryable
    Result<Value, JsonPathParserError>Queried<Queryable>
    JsonPath#{query_with_path, query_only_path, query}Operates on Queryable
    JsonPathParserErrorJsonPathError
    in, nin, anyOf, noneOf, subsetOfin, nin, any_of, none_of, subset_of (renamed to snake_case)

    Note: The length function was removed. Use Rust's native length expressions within filters instead.

  3. Implement the Queryable trait for custom data structures

    main

    The Queryable trait 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 implement Queryable and satisfy several trait bounds including Default, Clone, Debug, and conversions from &str, bool, i64, f64, Vec<Self>, and String.

    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");
  4. Nesting depth limits in JSONPath parsing

    main

    To protect against stack overflow during recursive parsing, jsonpath-rust enforces a maximum nesting depth for parentheses () and brackets [].

    • The limit is set by MAX_NESTING_DEPTH (currently 128).
    • 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.
  5. Modify JSON data using paths

    main

    The library allows for targeted updates to JSON structures. While the specific reference_mut and delete_by_path methods are part of the broader API (often accessed via traits on Queryable types), the pattern involves:

    1. Finding the path of the element to modify using query_only_path.
    2. 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)
    }
  6. Filter RFC 9535 test cases

    main

    You can exclude specific test cases from the compliance suite by using the filtered_cases.json file. 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"
      }
    ]
  7. Perform high-performance queries with `js_path_process`

    main

    If 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 JpQuery object and use js_path_process to execute it.

    1. Parse the path using parse_json_path(path) to get a JpQuery.
    2. 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)?;
  8. Use JsonPath extensions for array filtering

    main

    The 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)]
  9. Update JSON structure by path

    main

    The library does not support updating JSON directly within a query string. Instead, you must find the path using a query and then use the Queryable trait's reference methods to mutate the structure manually.

    Supported path elements for direct access:

    • root
    • field
    • index

    Methods:

    • reference_mut: Returns an Option<&mut Self> for a given path.
    • reference: Returns an Option<&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()
    }
  10. Query JSON data with paths and values

    main

    The Queryable trait 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()
    }