From 3eb0149bf33839850f958c20b63cf8b4c61cfaf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=A4rtens?= Date: Tue, 11 Jun 2024 20:20:30 +0200 Subject: [PATCH] cleanup after 0.6.0 changes (#66) * mention the License via name, so that its shown on https://crates.io/crates/jsonpath-rust instead of "non-standart", update to rust 2021 * provide a minimal example code, fix clippy issues and harden CI to verify everything * update README.md with most current usage. try to keep it simple, and link to docs as they are actually tested in CI via `cargo test` --- .github/workflows/ci.yml | 2 +- Cargo.toml | 4 +- README.md | 183 +++++---------------------------------- benches/equal.rs | 4 +- benches/regex.rs | 6 +- examples/hello-world.rs | 13 +++ src/lib.rs | 56 +++++++++++- src/parser/parser.rs | 2 + 8 files changed, 101 insertions(+), 169 deletions(-) create mode 100644 examples/hello-world.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 66f5835..b04abc1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,7 +28,7 @@ jobs: profile: minimal toolchain: stable components: clippy - - run: cargo clippy --workspace --tests --all-features -- -D warnings + - run: cargo clippy --workspace --all-targets --all-features -- -D warnings test: runs-on: ubuntu-latest diff --git a/Cargo.toml b/Cargo.toml index d709ff7..dd4b35c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,8 +3,8 @@ name = "jsonpath-rust" description = "The library provides the basic functionality to find the set of the data according to the filtering query." version = "0.6.0" authors = ["BorisZhguchev "] -edition = "2018" -license-file = "LICENSE" +edition = "2021" +license = "MIT" homepage = "https://github.com/besok/jsonpath-rust" repository = "https://github.com/besok/jsonpath-rust" readme = "README.md" diff --git a/README.md b/README.md index 8384f80..a46dbc0 100644 --- a/README.md +++ b/README.md @@ -231,7 +231,7 @@ Given the json | `$..book[?(@.author ~= '(?i)REES')]` | All books matching regex (ignore case) | | `$..*` | Give me every thing | -### The library +## Library Usage The library intends to provide the basic functionality for ability to find the slices of data using the syntax, saying above. The dependency can be found as following: @@ -251,180 +251,43 @@ To extract data there are two methods, provided on the `value`: ```rust let v:JsonPathValue =... v.to_data(); -v.slice_or( & some_dafult_value) - +v.slice_or(&some_dafault_value) ``` -```rust -use jsonpath_rust::JsonPathFinder; -use serde_json::{json, Value, JsonPathValue}; +### Find -fn main() { - let finder = JsonPathFinder::from_str(r#"{"first":{"second":[{"active":1},{"passive":1}]}}"#, "$.first.second[?(@.active)]").unwrap(); - let slice_of_data: Vec<&Value> = finder.find_slice(); - let js = json!({"active":1}); - assert_eq!(slice_of_data, vec![JsonPathValue::Slice(&js,"$.first.second[0]".to_string())]); -} -``` +there are 3 different functions to find data inside a `value`. +All take references, to increase reusability. Especially json parsing and jsonpath parsing can take significant time, compared to a simple find. -or with a separate instantiation: +The methods `find`, `find_as_path` and `find_slice` take the same inputs, but handle them differently depending on your usecase. They are further described in the [docs](https://docs.rs/jsonpath-rust/latest/jsonpath_rust/index.html#functions). ```rust -use serde_json::{json, Value}; -use crate::jsonpath_rust::{JsonPathFinder, JsonPathQuery, JsonPathInst, JsonPathValue}; +use jsonpath_rust::{JsonPathInst, JsonPathValue}; +use serde_json::json; use std::str::FromStr; -fn test() { - let json: Value = serde_json::from_str("{}").unwrap(); - let v = json.path("$..book[?(@.author size 10)].title").unwrap(); - assert_eq!(v, json!([])); - - let json: Value = serde_json::from_str("{}").unwrap(); - let path = &json.path("$..book[?(@.author size 10)].title").unwrap(); - - assert_eq!(path, &json!(["Sayings of the Century"])); - - let json: Box = serde_json::from_str("{}").unwrap(); - let path: Box = Box::from(JsonPathInst::from_str("$..book[?(@.author size 10)].title").unwrap()); - let finder = JsonPathFinder::new(json, path); - - let v = finder.find_slice(); - let js = json!("Sayings of the Century"); - assert_eq!(v, vec![JsonPathValue::Slice(&js,"$.book[0].title".to_string())]); -} - -``` -In case, if there is no match `find_slice` will return `vec![NoValue]` and `find` return `json!(null)` - -```rust -use jsonpath_rust::JsonPathFinder; -use serde_json::{json, Value, JsonPathValue}; - -fn main() { - let finder = JsonPathFinder::from_str(r#"{"first":{"second":[{"active":1},{"passive":1}]}}"#, "$.no_field").unwrap(); - let res_js = finder.find(); - assert_eq!(res_js, json!(null)); -} -``` - -also, it will work with the instances of [[Value]] as well. - -```rust - use serde_json::Value; -use crate::jsonpath_rust::{JsonPathFinder, JsonPathQuery, JsonPathInst}; -use crate::path::{json_path_instance, PathInstance}; - -fn test(json: Box, path: &str) { - let path = JsonPathInst::from_str(path).unwrap(); - JsonPathFinder::new(json, path) -} -``` - -also, the trait `JsonPathQuery` can be used: - -```rust - -use serde_json::{json, Value}; -use jsonpath_rust::JsonPathQuery; - -fn test() { - let json: Value = serde_json::from_str("{}").unwrap(); - let v = json.path("$..book[?(@.author size 10)].title").unwrap(); - assert_eq!(v, json!([])); - - let json: Value = serde_json::from_str(template_json()).unwrap(); - let path = &json.path("$..book[?(@.author size 10)].title").unwrap(); - - assert_eq!(path, &json!(["Sayings of the Century"])); -} -``` - -also, `JsonPathInst` can be used to query the data without cloning. -```rust -use serde_json::{json, Value}; -use crate::jsonpath_rust::{JsonPathInst}; - -fn test() { - let json: Value = serde_json::from_str("{}").expect("to get json"); - let query = JsonPathInst::from_str("$..book[?(@.author size 10)].title").unwrap(); - - // To convert to &Value, use deref() - assert_eq!(query.find_slice(&json).get(0).expect("to get value").deref(), &json!("Sayings of the Century")); -} -``` - -The library can return a path describing the value instead of the value itself. -To do that, the method `find_as_path` can be used: - -```rust -use jsonpath_rust::JsonPathFinder; -use serde_json::{json, Value, JsonPathValue}; - fn main() { - let finder = JsonPathFinder::from_str(r#"{"first":{"second":[{"active":1},{"passive":1}]}}"#, "$.first.second[?(@.active)]").unwrap(); - let slice_of_data: Value = finder.find_as_path(); - assert_eq!(slice_of_data, Value::Array(vec!["$.first.second[0]".to_string()])); -} -``` + let data = json!({"first":{"second":[{"active":1},{"passive":1}]}}); + let path = JsonPathInst::from_str("$.first.second[?(@.active)]").unwrap(); + let slice_of_data = jsonpath_rust::find_slice(&path, &data); -or it can be taken from the `JsonPathValue` instance: -```rust -use serde_json::{json, Value}; -use crate::jsonpath_rust::{JsonPathFinder, JsonPathQuery, JsonPathInst, JsonPathValue}; -use std::str::FromStr; - -fn test() { - let json: Box = serde_json::from_str("{}").unwrap(); - let path: Box = Box::from(JsonPathInst::from_str("$..book[?(@.author size 10)].title").unwrap()); - let finder = JsonPathFinder::new(json, path); + let expected_value = json!({"active":1}); + let expected_path = "$.['first'].['second'][0]".to_string(); - let v = finder.find_slice(); - let js = json!("Sayings of the Century"); - - // Slice has a path of its value as well - assert_eq!(v, vec![JsonPathValue::Slice(&js,"$.book[0].title".to_string())]); + assert_eq!( + slice_of_data, + vec![JsonPathValue::Slice(&expected_value, expected_path)] + ); } ``` -** If the value has been modified during the search, there is no way to find a path of a new value. -It can happen if we try to find a length() of array, for in stance.** - +### The structure +The internal structure of the `JsonPath` can be found here: +https://docs.rs/jsonpath-rust/latest/jsonpath_rust/parser/model/enum.JsonPath.html -## The structure - -```rust -pub enum JsonPath { - Root, - // <- $ - Field(String), - // <- field of the object - Chain(Vec), - // <- the whole jsonpath - Descent(String), - // <- '..' - Index(JsonPathIndex), - // <- the set of indexes represented by the next structure [[JsonPathIndex]] - Current(Box), - // <- @ - Wildcard, - // <- * - Empty, // the structure to avoid inconsistency -} - -pub enum JsonPathIndex { - Single(usize), - // <- [1] - UnionIndex(Vec), - // <- [1,2,3] - UnionKeys(Vec), - // <- ['key_1','key_2'] - Slice(i32, i32, usize), - // [0:10:1] - Filter(Operand, FilterSign, Operand), // <- [?(operand sign operand)] -} - -``` +The internal structure of the `JsonPathIndex` can be found here: +https://docs.rs/jsonpath-rust/latest/jsonpath_rust/parser/model/enum.JsonPathIndex.html ## How to contribute @@ -434,4 +297,4 @@ TBD - update files - commit them - add tag `git tag -a v -m "message"` - - git push origin \ No newline at end of file + - git push origin diff --git a/benches/equal.rs b/benches/equal.rs index 10104dc..134cea7 100644 --- a/benches/equal.rs +++ b/benches/equal.rs @@ -8,7 +8,7 @@ struct SearchData { path: JsonPathInst, } -const PATH: &'static str = "$.[?(@.author == 'abcd(Rees)')]"; +const PATH: &str = "$.[?(@.author == 'abcd(Rees)')]"; fn equal_perf_test_with_reuse(cfg: &SearchData) { let _v = jsonpath_rust::find(&cfg.path, &cfg.json); @@ -33,7 +33,7 @@ pub fn criterion_benchmark(c: &mut Criterion) { b.iter(|| equal_perf_test_with_reuse(&data)) }); c.bench_function("equal bench without reuse", |b| { - b.iter(|| equal_perf_test_without_reuse()) + b.iter(equal_perf_test_without_reuse) }); } diff --git a/benches/regex.rs b/benches/regex.rs index 6e0941d..901b688 100644 --- a/benches/regex.rs +++ b/benches/regex.rs @@ -8,7 +8,7 @@ struct SearchData { path: JsonPathInst, } -const PATH: &'static str = "$.[?(@.author ~= '.*(?i)d\\(Rees\\)')]"; +const PATH: &str = "$.[?(@.author ~= '.*(?i)d\\(Rees\\)')]"; fn regex_perf_test_with_reuse(cfg: &SearchData) { let _v = jsonpath_rust::find(&cfg.path, &cfg.json); @@ -37,10 +37,10 @@ pub fn criterion_benchmark(c: &mut Criterion) { b.iter(|| regex_perf_test_with_reuse(&data)) }); c.bench_function("regex bench without reuse", |b| { - b.iter(|| regex_perf_test_without_reuse()) + b.iter(regex_perf_test_without_reuse) }); c.bench_function("JsonPathInst generation", |b| { - b.iter(|| json_path_inst_compiling()) + b.iter(json_path_inst_compiling) }); } diff --git a/examples/hello-world.rs b/examples/hello-world.rs new file mode 100644 index 0000000..8fff1f7 --- /dev/null +++ b/examples/hello-world.rs @@ -0,0 +1,13 @@ +use jsonpath_rust::JsonPathInst; +use serde_json::json; +use std::str::FromStr; + +fn main() { + let data = json!({ + "Hello":"World", + "Good":"Bye", + }); + let path = JsonPathInst::from_str("$.Hello").unwrap(); + let search_result = jsonpath_rust::find(&path, &data); + println!("Hello, {}", search_result); +} diff --git a/src/lib.rs b/src/lib.rs index 1e0fd25..0cc09eb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -397,6 +397,27 @@ impl<'a, Data> JsonPathValue<'a, Data> { /// finds a slice of data in the set json. /// The result is a vector of references to the incoming structure. +/// +/// In case, if there is no match `find_slice` will return `vec![NoValue]`. +/// +/// ## Example +/// ```rust +/// use jsonpath_rust::{JsonPathInst, JsonPathValue}; +/// use serde_json::json; +/// # use std::str::FromStr; +/// +/// let data = json!({"first":{"second":[{"active":1},{"passive":1}]}}); +/// let path = JsonPathInst::from_str("$.first.second[?(@.active)]").unwrap(); +/// let slice_of_data = jsonpath_rust::find_slice(&path, &data); +/// +/// let expected_value = json!({"active":1}); +/// let expected_path = "$.['first'].['second'][0]".to_string(); +/// +/// assert_eq!( +/// slice_of_data, +/// vec![JsonPathValue::Slice(&expected_value, expected_path)] +/// ); +/// ``` pub fn find_slice<'a>(path: &'a JsonPathInst, json: &'a Value) -> Vec> { let instance = json_path_instance(&path.inner, json); let res = instance.find(JsonPathValue::from_root(json)); @@ -411,6 +432,21 @@ pub fn find_slice<'a>(path: &'a JsonPathInst, json: &'a Value) -> Vec Value { let slice = find_slice(path, json); if !slice.is_empty() { @@ -429,8 +465,26 @@ pub fn find(path: &JsonPathInst, json: &Value) -> Value { Value::Array(vec![]) } } -/// finds a path of the values. + +/// finds a path describing the value, instead of the value itself. /// If the values has been obtained by moving the data out of the initial json the path is absent. +/// +/// ** If the value has been modified during the search, there is no way to find a path of a new value. +/// It can happen if we try to find a length() of array, for in stance.** +/// +/// ## Example +/// ```rust +/// use jsonpath_rust::{JsonPathInst, JsonPathValue}; +/// use serde_json::{Value, json}; +/// # use std::str::FromStr; +/// +/// let data = json!({"first":{"second":[{"active":1},{"passive":1}]}}); +/// let path = JsonPathInst::from_str("$.first.second[?(@.active)]").unwrap(); +/// let slice_of_data: Value = jsonpath_rust::find_as_path(&path, &data); +/// +/// let expected_path = "$.['first'].['second'][0]".to_string(); +/// assert_eq!(slice_of_data, Value::Array(vec![Value::String(expected_path)])); +/// ``` pub fn find_as_path(path: &JsonPathInst, json: &Value) -> Value { Value::Array( find_slice(path, json) diff --git a/src/parser/parser.rs b/src/parser/parser.rs index 4155b7e..086ba4c 100644 --- a/src/parser/parser.rs +++ b/src/parser/parser.rs @@ -1,3 +1,5 @@ +#![allow(clippy::empty_docs)] + use crate::parser::errors::JsonPathParserError::ParserError; use crate::parser::errors::{parser_err, JsonPathParserError}; use crate::parser::model::FilterExpression::{And, Not, Or};