diff --git a/README.md b/README.md index a46dbc0..0e5fb49 100644 --- a/README.md +++ b/README.md @@ -256,20 +256,20 @@ v.slice_or(&some_dafault_value) ### Find -there are 3 different functions to find data inside a `value`. +there are 4 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. -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). +The methods `find`, `find_as_path`, `find_slice` and `find_slice_ptr` 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/enum.JsonPath.html#implementations). ```rust -use jsonpath_rust::{JsonPathInst, JsonPathValue}; +use jsonpath_rust::{JsonPath, JsonPathValue}; use serde_json::json; use std::str::FromStr; fn main() { 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 path = JsonPath::from_str("$.first.second[?(@.active)]").unwrap(); + let slice_of_data = path.find_slice(&data); let expected_value = json!({"active":1}); let expected_path = "$.['first'].['second'][0]".to_string(); diff --git a/benches/equal.rs b/benches/equal.rs index 134cea7..a549ca6 100644 --- a/benches/equal.rs +++ b/benches/equal.rs @@ -1,17 +1,17 @@ use criterion::{criterion_group, criterion_main, Criterion}; -use jsonpath_rust::{JsonPathInst, JsonPathQuery}; +use jsonpath_rust::{JsonPath, JsonPathQuery}; use serde_json::json; use std::str::FromStr; struct SearchData { json: serde_json::Value, - path: JsonPathInst, + path: JsonPath, } const PATH: &str = "$.[?(@.author == 'abcd(Rees)')]"; fn equal_perf_test_with_reuse(cfg: &SearchData) { - let _v = jsonpath_rust::find(&cfg.path, &cfg.json); + let _v = cfg.path.find(&cfg.json); } fn equal_perf_test_without_reuse() { @@ -27,7 +27,7 @@ pub fn criterion_benchmark(c: &mut Criterion) { json: json!({ "author":"abcd(Rees)", }), - path: JsonPathInst::from_str(PATH).unwrap(), + path: JsonPath::from_str(PATH).unwrap(), }; c.bench_function("equal bench with reuse", |b| { b.iter(|| equal_perf_test_with_reuse(&data)) diff --git a/benches/regex.rs b/benches/regex.rs index 901b688..0ee1ef9 100644 --- a/benches/regex.rs +++ b/benches/regex.rs @@ -1,17 +1,17 @@ use criterion::{criterion_group, criterion_main, Criterion}; -use jsonpath_rust::{JsonPathInst, JsonPathQuery}; +use jsonpath_rust::{JsonPath, JsonPathQuery}; use serde_json::json; use std::str::FromStr; struct SearchData { json: serde_json::Value, - path: JsonPathInst, + path: JsonPath, } const PATH: &str = "$.[?(@.author ~= '.*(?i)d\\(Rees\\)')]"; fn regex_perf_test_with_reuse(cfg: &SearchData) { - let _v = jsonpath_rust::find(&cfg.path, &cfg.json); + let _v = cfg.path.find(&cfg.json); } fn regex_perf_test_without_reuse() { @@ -22,8 +22,8 @@ fn regex_perf_test_without_reuse() { let _v = json.path(PATH).expect("the path is correct"); } -fn json_path_inst_compiling() { - let _v = JsonPathInst::from_str(PATH).unwrap(); +fn json_path_compiling() { + let _v = JsonPath::from_str(PATH).unwrap(); } pub fn criterion_benchmark(c: &mut Criterion) { @@ -31,7 +31,7 @@ pub fn criterion_benchmark(c: &mut Criterion) { json: json!({ "author":"abcd(Rees)", }), - path: JsonPathInst::from_str(PATH).unwrap(), + path: JsonPath::from_str(PATH).unwrap(), }; c.bench_function("regex bench with reuse", |b| { b.iter(|| regex_perf_test_with_reuse(&data)) @@ -39,9 +39,7 @@ pub fn criterion_benchmark(c: &mut Criterion) { c.bench_function("regex bench without reuse", |b| { b.iter(regex_perf_test_without_reuse) }); - c.bench_function("JsonPathInst generation", |b| { - b.iter(json_path_inst_compiling) - }); + c.bench_function("JsonPath generation", |b| b.iter(json_path_compiling)); } criterion_group!(benches, criterion_benchmark); diff --git a/examples/hello-world.rs b/examples/hello-world.rs index 8fff1f7..d4716d2 100644 --- a/examples/hello-world.rs +++ b/examples/hello-world.rs @@ -1,13 +1,12 @@ -use jsonpath_rust::JsonPathInst; +use jsonpath_rust::JsonPath; 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); + let path = JsonPath::try_from("$.Hello").unwrap(); + let search_result = path.find(&data); println!("Hello, {}", search_result); } diff --git a/src/jsonpath.rs b/src/jsonpath.rs new file mode 100644 index 0000000..94daa6d --- /dev/null +++ b/src/jsonpath.rs @@ -0,0 +1,915 @@ +use crate::path::json_path_instance; +use crate::JsonPath; +use crate::JsonPathValue; +use crate::JsonPathValue::NoValue; +use crate::JsonPtr; +use crate::Value; + +impl JsonPath { + /// 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 [`Self::find_slice`] will return vec!<[`JsonPathValue::NoValue`]>. + /// + /// ## Example + /// ```rust + /// use jsonpath_rust::{JsonPath, JsonPathValue}; + /// use serde_json::json; + /// # use std::str::FromStr; + /// + /// let data = json!({"first":{"second":[{"active":1},{"passive":1}]}}); + /// let path = JsonPath::try_from("$.first.second[?(@.active)]").unwrap(); + /// let slice_of_data = path.find_slice(&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>(&'a self, json: &'a Value) -> Vec> { + use crate::path::Path; + let instance = json_path_instance(self, json); + let res = instance.find(JsonPathValue::from_root(json)); + let has_v: Vec> = + res.into_iter().filter(|v| v.has_value()).collect(); + + if has_v.is_empty() { + vec![NoValue] + } else { + has_v + } + } + + /// like [`Self::find_slice`] but returns a vector of [`JsonPtr`], which has no [`JsonPathValue::NoValue`]. + /// if there is no match, it will return an empty vector + pub fn find_slice_ptr<'a>(&'a self, json: &'a Value) -> Vec> { + use crate::path::Path; + json_path_instance(self, json) + .find(JsonPathValue::from_root(json)) + .into_iter() + .filter(|v| v.has_value()) + .map(|v| match v { + JsonPathValue::Slice(v, _) => JsonPtr::Slice(v), + JsonPathValue::NewValue(v) => JsonPtr::NewValue(v), + JsonPathValue::NoValue => unreachable!("has_value was already checked"), + }) + .collect() + } + + /// finds a slice of data and wrap it with Value::Array by cloning the data. + /// Returns either an array of elements or Json::Null if the match is incorrect. + /// + /// In case, if there is no match `find` will return `json!(null)`. + /// + /// ## Example + /// ```rust + /// use jsonpath_rust::{JsonPath, JsonPathValue}; + /// use serde_json::{Value, json}; + /// # use std::str::FromStr; + /// + /// let data = json!({"first":{"second":[{"active":1},{"passive":1}]}}); + /// let path = JsonPath::try_from("$.first.second[?(@.active)]").unwrap(); + /// let cloned_data = path.find(&data); + /// + /// assert_eq!(cloned_data, Value::Array(vec![json!({"active":1})])); + /// ``` + pub fn find(&self, json: &Value) -> Value { + let slice = self.find_slice(json); + if !slice.is_empty() { + if JsonPathValue::only_no_value(&slice) { + Value::Null + } else { + Value::Array( + slice + .into_iter() + .filter(|v| v.has_value()) + .map(|v| v.to_data()) + .collect(), + ) + } + } else { + Value::Array(vec![]) + } + } + + /// 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::{JsonPath, JsonPathValue}; + /// use serde_json::{Value, json}; + /// # use std::str::FromStr; + /// + /// let data = json!({"first":{"second":[{"active":1},{"passive":1}]}}); + /// let path = JsonPath::try_from("$.first.second[?(@.active)]").unwrap(); + /// let slice_of_data: Value = path.find_as_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(&self, json: &Value) -> Value { + Value::Array( + self.find_slice(json) + .into_iter() + .flat_map(|v| v.to_path()) + .map(|v| v.into()) + .collect(), + ) + } +} + +#[cfg(test)] +mod tests { + use crate::JsonPathQuery; + use crate::JsonPathValue::{NoValue, Slice}; + use crate::{jp_v, JsonPath, JsonPathValue}; + use serde_json::{json, Value}; + use std::ops::Deref; + + fn test(json: &str, path: &str, expected: Vec>) { + let json: Value = match serde_json::from_str(json) { + Ok(json) => json, + Err(e) => panic!("error while parsing json: {}", e), + }; + let path = match JsonPath::try_from(path) { + Ok(path) => path, + Err(e) => panic!("error while parsing jsonpath: {}", e), + }; + + assert_eq!(path.find_slice(&json), expected) + } + + fn template_json<'a>() -> &'a str { + r#" {"store": { "book": [ + { + "category": "reference", + "author": "Nigel Rees", + "title": "Sayings of the Century", + "price": 8.95 + }, + { + "category": "fiction", + "author": "Evelyn Waugh", + "title": "Sword of Honour", + "price": 12.99 + }, + { + "category": "fiction", + "author": "Herman Melville", + "title": "Moby Dick", + "isbn": "0-553-21311-3", + "price": 8.99 + }, + { + "category": "fiction", + "author": "J. R. R. Tolkien", + "title": "The Lord of the Rings", + "isbn": "0-395-19395-8", + "price": 22.99 + } + ], + "bicycle": { + "color": "red", + "price": 19.95 + } + }, + "array":[0,1,2,3,4,5,6,7,8,9], + "orders":[ + { + "ref":[1,2,3], + "id":1, + "filled": true + }, + { + "ref":[4,5,6], + "id":2, + "filled": false + }, + { + "ref":[7,8,9], + "id":3, + "filled": null + } + ], + "expensive": 10 }"# + } + + #[test] + fn simple_test() { + let j1 = json!(2); + test("[1,2,3]", "$[1]", jp_v![&j1;"$[1]",]); + } + + #[test] + fn root_test() { + let js = serde_json::from_str(template_json()).unwrap(); + test(template_json(), "$", jp_v![&js;"$",]); + } + + #[test] + fn descent_test() { + let v1 = json!("reference"); + let v2 = json!("fiction"); + test( + template_json(), + "$..category", + jp_v![ + &v1;"$.['store'].['book'][0].['category']", + &v2;"$.['store'].['book'][1].['category']", + &v2;"$.['store'].['book'][2].['category']", + &v2;"$.['store'].['book'][3].['category']",], + ); + let js1 = json!(19.95); + let js2 = json!(8.95); + let js3 = json!(12.99); + let js4 = json!(8.99); + let js5 = json!(22.99); + test( + template_json(), + "$.store..price", + jp_v![ + &js1;"$.['store'].['bicycle'].['price']", + &js2;"$.['store'].['book'][0].['price']", + &js3;"$.['store'].['book'][1].['price']", + &js4;"$.['store'].['book'][2].['price']", + &js5;"$.['store'].['book'][3].['price']", + ], + ); + let js1 = json!("Nigel Rees"); + let js2 = json!("Evelyn Waugh"); + let js3 = json!("Herman Melville"); + let js4 = json!("J. R. R. Tolkien"); + test( + template_json(), + "$..author", + jp_v![ + &js1;"$.['store'].['book'][0].['author']", + &js2;"$.['store'].['book'][1].['author']", + &js3;"$.['store'].['book'][2].['author']", + &js4;"$.['store'].['book'][3].['author']",], + ); + } + + #[test] + fn wildcard_test() { + let js1 = json!("reference"); + let js2 = json!("fiction"); + test( + template_json(), + "$..book.[*].category", + jp_v![ + &js1;"$.['store'].['book'][0].['category']", + &js2;"$.['store'].['book'][1].['category']", + &js2;"$.['store'].['book'][2].['category']", + &js2;"$.['store'].['book'][3].['category']",], + ); + let js1 = json!("Nigel Rees"); + let js2 = json!("Evelyn Waugh"); + let js3 = json!("Herman Melville"); + let js4 = json!("J. R. R. Tolkien"); + test( + template_json(), + "$.store.book[*].author", + jp_v![ + &js1;"$.['store'].['book'][0].['author']", + &js2;"$.['store'].['book'][1].['author']", + &js3;"$.['store'].['book'][2].['author']", + &js4;"$.['store'].['book'][3].['author']",], + ); + } + + #[test] + fn descendent_wildcard_test() { + let js1 = json!("Moby Dick"); + let js2 = json!("The Lord of the Rings"); + test( + template_json(), + "$..*.[?(@.isbn)].title", + jp_v![ + &js1;"$.['store'].['book'][2].['title']", + &js2;"$.['store'].['book'][3].['title']", + &js1;"$.['store'].['book'][2].['title']", + &js2;"$.['store'].['book'][3].['title']"], + ); + } + + #[test] + fn field_test() { + let value = json!({"active":1}); + test( + r#"{"field":{"field":[{"active":1},{"passive":1}]}}"#, + "$.field.field[?(@.active)]", + jp_v![&value;"$.['field'].['field'][0]",], + ); + } + + #[test] + fn index_index_test() { + let value = json!("0-553-21311-3"); + test( + template_json(), + "$..book[2].isbn", + jp_v![&value;"$.['store'].['book'][2].['isbn']",], + ); + } + + #[test] + fn index_unit_index_test() { + let value = json!("0-553-21311-3"); + test( + template_json(), + "$..book[2,4].isbn", + jp_v![&value;"$.['store'].['book'][2].['isbn']",], + ); + let value1 = json!("0-395-19395-8"); + test( + template_json(), + "$..book[2,3].isbn", + jp_v![&value;"$.['store'].['book'][2].['isbn']", &value1;"$.['store'].['book'][3].['isbn']",], + ); + } + + #[test] + fn index_unit_keys_test() { + let js1 = json!("Moby Dick"); + let js2 = json!(8.99); + let js3 = json!("The Lord of the Rings"); + let js4 = json!(22.99); + test( + template_json(), + "$..book[2,3]['title','price']", + jp_v![ + &js1;"$.['store'].['book'][2].['title']", + &js2;"$.['store'].['book'][2].['price']", + &js3;"$.['store'].['book'][3].['title']", + &js4;"$.['store'].['book'][3].['price']",], + ); + } + + #[test] + fn index_slice_test() { + let i0 = "$.['array'][0]"; + let i1 = "$.['array'][1]"; + let i2 = "$.['array'][2]"; + let i3 = "$.['array'][3]"; + let i4 = "$.['array'][4]"; + let i5 = "$.['array'][5]"; + let i6 = "$.['array'][6]"; + let i7 = "$.['array'][7]"; + let i8 = "$.['array'][8]"; + let i9 = "$.['array'][9]"; + + let j0 = json!(0); + let j1 = json!(1); + let j2 = json!(2); + let j3 = json!(3); + let j4 = json!(4); + let j5 = json!(5); + let j6 = json!(6); + let j7 = json!(7); + let j8 = json!(8); + let j9 = json!(9); + test( + template_json(), + "$.array[:]", + jp_v![ + &j0;&i0, + &j1;&i1, + &j2;&i2, + &j3;&i3, + &j4;&i4, + &j5;&i5, + &j6;&i6, + &j7;&i7, + &j8;&i8, + &j9;&i9,], + ); + test(template_json(), "$.array[1:4:2]", jp_v![&j1;&i1, &j3;&i3,]); + test( + template_json(), + "$.array[::3]", + jp_v![&j0;&i0, &j3;&i3, &j6;&i6, &j9;&i9,], + ); + test(template_json(), "$.array[-1:]", jp_v![&j9;&i9,]); + test(template_json(), "$.array[-2:-1]", jp_v![&j8;&i8,]); + } + + #[test] + fn index_filter_test() { + let moby = json!("Moby Dick"); + let rings = json!("The Lord of the Rings"); + test( + template_json(), + "$..book[?(@.isbn)].title", + jp_v![ + &moby;"$.['store'].['book'][2].['title']", + &rings;"$.['store'].['book'][3].['title']",], + ); + let sword = json!("Sword of Honour"); + test( + template_json(), + "$..book[?(@.price != 8.95)].title", + jp_v![ + &sword;"$.['store'].['book'][1].['title']", + &moby;"$.['store'].['book'][2].['title']", + &rings;"$.['store'].['book'][3].['title']",], + ); + let sayings = json!("Sayings of the Century"); + test( + template_json(), + "$..book[?(@.price == 8.95)].title", + jp_v![&sayings;"$.['store'].['book'][0].['title']",], + ); + let js895 = json!(8.95); + test( + template_json(), + "$..book[?(@.author ~= '.*Rees')].price", + jp_v![&js895;"$.['store'].['book'][0].['price']",], + ); + let js12 = json!(12.99); + let js899 = json!(8.99); + let js2299 = json!(22.99); + test( + template_json(), + "$..book[?(@.price >= 8.99)].price", + jp_v![ + &js12;"$.['store'].['book'][1].['price']", + &js899;"$.['store'].['book'][2].['price']", + &js2299;"$.['store'].['book'][3].['price']", + ], + ); + test( + template_json(), + "$..book[?(@.price > 8.99)].price", + jp_v![ + &js12;"$.['store'].['book'][1].['price']", + &js2299;"$.['store'].['book'][3].['price']",], + ); + test( + template_json(), + "$..book[?(@.price < 8.99)].price", + jp_v![&js895;"$.['store'].['book'][0].['price']",], + ); + test( + template_json(), + "$..book[?(@.price <= 8.99)].price", + jp_v![ + &js895;"$.['store'].['book'][0].['price']", + &js899;"$.['store'].['book'][2].['price']", + ], + ); + test( + template_json(), + "$..book[?(@.price <= $.expensive)].price", + jp_v![ + &js895;"$.['store'].['book'][0].['price']", + &js899;"$.['store'].['book'][2].['price']", + ], + ); + test( + template_json(), + "$..book[?(@.price >= $.expensive)].price", + jp_v![ + &js12;"$.['store'].['book'][1].['price']", + &js2299;"$.['store'].['book'][3].['price']", + ], + ); + test( + template_json(), + "$..book[?(@.title in ['Moby Dick','Shmoby Dick','Big Dick','Dicks'])].price", + jp_v![&js899;"$.['store'].['book'][2].['price']",], + ); + test( + template_json(), + "$..book[?(@.title nin ['Moby Dick','Shmoby Dick','Big Dick','Dicks'])].title", + jp_v![ + &sayings;"$.['store'].['book'][0].['title']", + &sword;"$.['store'].['book'][1].['title']", + &rings;"$.['store'].['book'][3].['title']",], + ); + test( + template_json(), + "$..book[?(@.author size 10)].title", + jp_v![&sayings;"$.['store'].['book'][0].['title']",], + ); + let filled_true = json!(1); + test( + template_json(), + "$.orders[?(@.filled == true)].id", + jp_v![&filled_true;"$.['orders'][0].['id']",], + ); + let filled_null = json!(3); + test( + template_json(), + "$.orders[?(@.filled == null)].id", + jp_v![&filled_null;"$.['orders'][2].['id']",], + ); + } + + #[test] + fn index_filter_sets_test() { + let j1 = json!(1); + test( + template_json(), + "$.orders[?(@.ref subsetOf [1,2,3,4])].id", + jp_v![&j1;"$.['orders'][0].['id']",], + ); + let j2 = json!(2); + test( + template_json(), + "$.orders[?(@.ref anyOf [1,4])].id", + jp_v![&j1;"$.['orders'][0].['id']", &j2;"$.['orders'][1].['id']",], + ); + let j3 = json!(3); + test( + template_json(), + "$.orders[?(@.ref noneOf [3,6])].id", + jp_v![&j3;"$.['orders'][2].['id']",], + ); + } + + #[test] + fn query_test() { + let json: Box = serde_json::from_str(template_json()).expect("to get json"); + let v = json + .path("$..book[?(@.author size 10)].title") + .expect("the path is correct"); + assert_eq!(v, json!(["Sayings of the Century"])); + + let json: Value = serde_json::from_str(template_json()).expect("to get json"); + let path = &json + .path("$..book[?(@.author size 10)].title") + .expect("the path is correct"); + + assert_eq!(path, &json!(["Sayings of the Century"])); + } + + #[test] + fn find_slice_test() { + let json: Box = serde_json::from_str(template_json()).expect("to get json"); + let path: Box = Box::from( + JsonPath::try_from("$..book[?(@.author size 10)].title").expect("the path is correct"), + ); + let v = path.find_slice(&json); + let js = json!("Sayings of the Century"); + assert_eq!(v, jp_v![&js;"$.['store'].['book'][0].['title']",]); + } + + #[test] + fn find_in_array_test() { + let json: Box = Box::new(json!([{"verb": "TEST"}, {"verb": "RUN"}])); + let path: Box = + Box::from(JsonPath::try_from("$.[?(@.verb == 'TEST')]").expect("the path is correct")); + let v = path.find_slice(&json); + let js = json!({"verb":"TEST"}); + assert_eq!(v, jp_v![&js;"$[0]",]); + } + + #[test] + fn length_test() { + let json: Box = + Box::new(json!([{"verb": "TEST"},{"verb": "TEST"}, {"verb": "RUN"}])); + let path: Box = Box::from( + JsonPath::try_from("$.[?(@.verb == 'TEST')].length()").expect("the path is correct"), + ); + let v = path.find(&json); + let js = json!([2]); + assert_eq!(v, js); + + let json: Box = + Box::new(json!([{"verb": "TEST"},{"verb": "TEST"}, {"verb": "RUN"}])); + let path: Box = + Box::from(JsonPath::try_from("$.length()").expect("the path is correct")); + assert_eq!(path.find(&json), json!([3])); + + // length of search following the wildcard returns correct result + let json: Box = + Box::new(json!([{"verb": "TEST"},{"verb": "TEST","x":3}, {"verb": "RUN"}])); + let path: Box = Box::from( + JsonPath::try_from("$.[?(@.verb == 'TEST')].[*].length()") + .expect("the path is correct"), + ); + assert_eq!(path.find(&json), json!([3])); + + // length of object returns 0 + let json: Box = Box::new(json!({"verb": "TEST"})); + let path: Box = + Box::from(JsonPath::try_from("$.length()").expect("the path is correct")); + assert_eq!(path.find(&json), Value::Null); + + // length of integer returns null + let json: Box = Box::new(json!(1)); + let path: Box = + Box::from(JsonPath::try_from("$.length()").expect("the path is correct")); + assert_eq!(path.find(&json), Value::Null); + + // length of array returns correct result + let json: Box = Box::new(json!([[1], [2], [3]])); + let path: Box = + Box::from(JsonPath::try_from("$.length()").expect("the path is correct")); + assert_eq!(path.find(&json), json!([3])); + + // path does not exist returns length null + let json: Box = + Box::new(json!([{"verb": "TEST"},{"verb": "TEST"}, {"verb": "RUN"}])); + let path: Box = + Box::from(JsonPath::try_from("$.not.exist.length()").expect("the path is correct")); + assert_eq!(path.find(&json), Value::Null); + + // seraching one value returns correct length + let json: Box = + Box::new(json!([{"verb": "TEST"},{"verb": "TEST"}, {"verb": "RUN"}])); + let path: Box = Box::from( + JsonPath::try_from("$.[?(@.verb == 'RUN')].length()").expect("the path is correct"), + ); + + let v = path.find(&json); + let js = json!([1]); + assert_eq!(v, js); + + // searching correct path following unexisting key returns length 0 + let json: Box = + Box::new(json!([{"verb": "TEST"},{"verb": "TEST"}, {"verb": "RUN"}])); + let path: Box = Box::from( + JsonPath::try_from("$.[?(@.verb == 'RUN')].key123.length()") + .expect("the path is correct"), + ); + + let v = path.find(&json); + let js = json!(null); + assert_eq!(v, js); + + // fetching first object returns length null + let json: Box = + Box::new(json!([{"verb": "TEST"},{"verb": "TEST"}, {"verb": "RUN"}])); + let path: Box = + Box::from(JsonPath::try_from("$.[0].length()").expect("the path is correct")); + + let v = path.find(&json); + let js = Value::Null; + assert_eq!(v, js); + + // length on fetching the index after search gives length of the object (array) + let json: Box = Box::new(json!([{"prop": [["a", "b", "c"], "d"]}])); + let path: Box = Box::from( + JsonPath::try_from("$.[?(@.prop)].prop.[0].length()").expect("the path is correct"), + ); + + let v = path.find(&json); + let js = json!([3]); + assert_eq!(v, js); + + // length on fetching the index after search gives length of the object (string) + let json: Box = Box::new(json!([{"prop": [["a", "b", "c"], "d"]}])); + let path: Box = Box::from( + JsonPath::try_from("$.[?(@.prop)].prop.[1].length()").expect("the path is correct"), + ); + + let v = path.find(&json); + let js = Value::Null; + assert_eq!(v, js); + } + + #[test] + fn no_value_index_from_not_arr_filter_test() { + let json: Box = Box::new(json!({ + "field":"field", + })); + + let path: Box = + Box::from(JsonPath::try_from("$.field[1]").expect("the path is correct")); + let v = path.find_slice(&json); + assert_eq!(v, vec![NoValue]); + + let json: Box = Box::new(json!({ + "field":[0], + })); + + let path: Box = + Box::from(JsonPath::try_from("$.field[1]").expect("the path is correct")); + let v = path.find_slice(&json); + assert_eq!(v, vec![NoValue]); + } + + #[test] + fn no_value_filter_from_not_arr_filter_test() { + let json: Box = Box::new(json!({ + "field":"field", + })); + + let path: Box = + Box::from(JsonPath::try_from("$.field[?(@ == 0)]").expect("the path is correct")); + let v = path.find_slice(&json); + assert_eq!(v, vec![NoValue]); + } + + #[test] + fn no_value_index_filter_test() { + let json: Box = Box::new(json!({ + "field":[{"f":1},{"f":0}], + })); + + let path: Box = + Box::from(JsonPath::try_from("$.field[?(@.f_ == 0)]").expect("the path is correct")); + let v = path.find_slice(&json); + assert_eq!(v, vec![NoValue]); + } + + #[test] + fn no_value_decent_test() { + let json: Box = Box::new(json!({ + "field":[{"f":1},{"f":{"f_":1}}], + })); + + let path: Box = + Box::from(JsonPath::try_from("$..f_").expect("the path is correct")); + let v = path.find_slice(&json); + assert_eq!( + v, + vec![Slice(&json!(1), "$.['field'][1].['f'].['f_']".to_string())] + ); + } + + #[test] + fn no_value_chain_test() { + let json: Box = Box::new(json!({ + "field":{"field":[1]}, + })); + + let path: Box = + Box::from(JsonPath::try_from("$.field_.field").expect("the path is correct")); + let v = path.find_slice(&json); + assert_eq!(v, vec![NoValue]); + + let path: Box = Box::from( + JsonPath::try_from("$.field_.field[?(@ == 1)]").expect("the path is correct"), + ); + let v = path.find_slice(&json); + assert_eq!(v, vec![NoValue]); + } + + #[test] + fn no_value_filter_test() { + // searching unexisting value returns length 0 + let json: Box = + Box::new(json!([{"verb": "TEST"},{"verb": "TEST"}, {"verb": "RUN"}])); + let path: Box = Box::from( + JsonPath::try_from("$.[?(@.verb == \"RUN1\")]").expect("the path is correct"), + ); + let v = path.find(&json); + let js = json!(null); + assert_eq!(v, js); + } + + #[test] + fn no_value_len_test() { + let json: Box = Box::new(json!({ + "field":{"field":1}, + })); + + let path: Box = + Box::from(JsonPath::try_from("$.field.field.length()").expect("the path is correct")); + let v = path.find_slice(&json); + assert_eq!(v, vec![NoValue]); + + let json: Box = Box::new(json!({ + "field":[{"a":1},{"a":1}], + })); + let path: Box = Box::from( + JsonPath::try_from("$.field[?(@.a == 0)].f.length()").expect("the path is correct"), + ); + let v = path.find_slice(&json); + assert_eq!(v, vec![NoValue]); + } + + #[test] + fn no_clone_api_test() { + fn test_coercion(value: &Value) -> Value { + value.clone() + } + + let json: Value = serde_json::from_str(template_json()).expect("to get json"); + let query = + JsonPath::try_from("$..book[?(@.author size 10)].title").expect("the path is correct"); + + let results = query.find_slice_ptr(&json); + let v = results.first().expect("to get value"); + + // V can be implicitly converted to &Value + test_coercion(v); + + // To explicitly convert to &Value, use deref() + assert_eq!(v.deref(), &json!("Sayings of the Century")); + } + + #[test] + fn logical_exp_test() { + let json: Box = Box::new(json!({"first":{"second":[{"active":1},{"passive":1}]}})); + + let path: Box = Box::from( + JsonPath::try_from("$.first[?(@.does_not_exist && @.does_not_exist >= 1.0)]") + .expect("the path is correct"), + ); + let v = path.find_slice(&json); + assert_eq!(v, vec![NoValue]); + + let path: Box = Box::from( + JsonPath::try_from("$.first[?(@.does_not_exist >= 1.0)]").expect("the path is correct"), + ); + let v = path.find_slice(&json); + assert_eq!(v, vec![NoValue]); + } + + #[test] + fn regex_filter_test() { + let json: Box = Box::new(json!({ + "author":"abcd(Rees)", + })); + + let path: Box = Box::from( + JsonPath::try_from("$.[?(@.author ~= '(?i)d\\(Rees\\)')]") + .expect("the path is correct"), + ); + assert_eq!( + path.find_slice(&json.clone()), + vec![Slice(&json!({"author":"abcd(Rees)"}), "$".to_string())] + ); + } + + #[test] + fn logical_not_exp_test() { + let json: Box = Box::new(json!({"first":{"second":{"active":1}}})); + let path: Box = Box::from( + JsonPath::try_from("$.first[?(!@.does_not_exist >= 1.0)]") + .expect("the path is correct"), + ); + let v = path.find_slice(&json); + assert_eq!( + v, + vec![Slice( + &json!({"second":{"active": 1}}), + "$.['first']".to_string() + )] + ); + + let path: Box = Box::from( + JsonPath::try_from("$.first[?(!(@.does_not_exist >= 1.0))]") + .expect("the path is correct"), + ); + let v = path.find_slice(&json); + assert_eq!( + v, + vec![Slice( + &json!({"second":{"active": 1}}), + "$.['first']".to_string() + )] + ); + + let path: Box = Box::from( + JsonPath::try_from("$.first[?(!(@.second.active == 1) || @.second.active == 1)]") + .expect("the path is correct"), + ); + let v = path.find_slice(&json); + assert_eq!( + v, + vec![Slice( + &json!({"second":{"active": 1}}), + "$.['first']".to_string() + )] + ); + + let path: Box = Box::from( + JsonPath::try_from("$.first[?(!@.second.active == 1 && !@.second.active == 1 || !@.second.active == 2)]") + .expect("the path is correct"), + ); + let v = path.find_slice(&json); + assert_eq!( + v, + vec![Slice( + &json!({"second":{"active": 1}}), + "$.['first']".to_string() + )] + ); + } + + // #[test] + // fn no_value_len_field_test() { + // let json: Box = + // Box::new(json!([{"verb": "TEST","a":[1,2,3]},{"verb": "TEST","a":[1,2,3]},{"verb": "TEST"}, {"verb": "RUN"}])); + // let path: Box = Box::from( + // JsonPath::try_from("$.[?(@.verb == 'TEST')].a.length()") + // .expect("the path is correct"), + // ); + // let finder = JsonPathFinder::new(json, path); + // + // let v = finder.find_slice(); + // assert_eq!(v, vec![NewValue(json!(3))]); + // } +} diff --git a/src/lib.rs b/src/lib.rs index 5025e9f..03baf25 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -83,12 +83,12 @@ //!```rust //! use std::str::FromStr; //! use serde_json::{json, Value}; -//! use jsonpath_rust::{jp_v, find_slice, JsonPathValue, JsonPathInst}; +//! use jsonpath_rust::{jp_v, JsonPathValue, JsonPath}; //! //! fn test() -> Result<(), Box> { //! let json = serde_json::from_str(r#"{"first":{"second":[{"active":1},{"passive":1}]}}"#)?; -//! let path = JsonPathInst::from_str("$.first.second[?(@.active)]")?; -//! let slice_of_data:Vec> = find_slice(&path, &json); +//! let path = JsonPath::try_from("$.first.second[?(@.active)]")?; +//! let slice_of_data:Vec> = path.find_slice(&json); //! let js = json!({"active":1}); //! assert_eq!(slice_of_data, jp_v![&js;"$.first.second[0]",]); //! # Ok(()) @@ -98,19 +98,14 @@ //! //! [`there`]: https://goessner.net/articles/JsonPath/ -#![allow(clippy::vec_init_then_push)] - -use crate::parser::model::JsonPath; -use crate::parser::parser::parse_json_path; -use crate::path::json_path_instance; -use parser::errors::JsonPathParserError; +pub use parser::model::JsonPath; +use parser::JsonPathParserError; use serde_json::Value; -use std::convert::TryInto; use std::fmt::Debug; use std::ops::Deref; -use std::str::FromStr; use JsonPathValue::{NewValue, NoValue, Slice}; +mod jsonpath; pub mod parser; pub mod path; @@ -119,14 +114,31 @@ extern crate pest_derive; extern crate core; extern crate pest; -/// the trait allows to mix the method path to the value of [Value] -/// and thus the using can be shortened to the following one: +/// the trait allows to query a path on any value by just passing the &str of as JsonPath. +/// +/// It is equal to +/// ```rust +/// # use serde_json::json; +/// # use std::str::FromStr; +/// use jsonpath_rust::JsonPath; +/// +/// let query = "$.hello"; +/// let json_path = JsonPath::from_str(query).unwrap(); +/// json_path.find(&json!({"hello": "world"})); +/// ``` +/// +/// It is default implemented for [Value]. +/// +/// #Note: +/// the result is going to be cloned and therefore it can be significant for the huge queries. +/// if the same &str is used multiple times, it's more efficient to reuse a single JsonPath. +/// /// # Examples: /// ``` /// use std::str::FromStr; /// use serde_json::{json, Value}; /// use jsonpath_rust::jp_v; -/// use jsonpath_rust::{find_slice, JsonPathQuery, JsonPathInst, JsonPathValue}; +/// use jsonpath_rust::{JsonPathQuery, JsonPath, JsonPathValue}; /// /// fn test() -> Result<(), Box> { /// let json: Value = serde_json::from_str("{}")?; @@ -139,52 +151,19 @@ extern crate pest; /// assert_eq!(path, json!(["Sayings of the Century"])); /// /// let json: Value = serde_json::from_str("{}")?; -/// let path: JsonPathInst = JsonPathInst::from_str("$..book[?(@.author size 10)].title")?; +/// let path = JsonPath::try_from("$..book[?(@.author size 10)].title")?; /// -/// let v = find_slice(&path, &json); +/// let v = path.find_slice(&json); /// let js = json!("Sayings of the Century"); /// assert_eq!(v, jp_v![&js;"",]); /// # Ok(()) /// } /// /// ``` -/// #Note: -/// the result is going to be cloned and therefore it can be significant for the huge queries pub trait JsonPathQuery { fn path(self, query: &str) -> Result; } -#[derive(Clone, Debug)] -pub struct JsonPathInst { - inner: JsonPath, -} - -impl FromStr for JsonPathInst { - type Err = JsonPathParserError; - - fn from_str(s: &str) -> Result { - Ok(JsonPathInst { - inner: s.try_into()?, - }) - } -} - -impl JsonPathInst { - pub fn find_slice<'a>(&'a self, value: &'a Value) -> Vec> { - use crate::path::Path; - json_path_instance(&self.inner, value) - .find(JsonPathValue::from_root(value)) - .into_iter() - .filter(|v| v.has_value()) - .map(|v| match v { - JsonPathValue::Slice(v, _) => JsonPtr::Slice(v), - JsonPathValue::NewValue(v) => JsonPtr::NewValue(v), - JsonPathValue::NoValue => unreachable!("has_value was already checked"), - }) - .collect() - } -} - /// Json paths may return either pointers to the original json or new data. This custom pointer type allows us to handle both cases. /// Unlike JsonPathValue, this type does not represent NoValue to allow the implementation of Deref. pub enum JsonPtr<'a, Data> { @@ -208,8 +187,8 @@ impl<'a> Deref for JsonPtr<'a, Value> { impl JsonPathQuery for Value { fn path(self, query: &str) -> Result { - let p = JsonPathInst::from_str(query)?; - Ok(find(&p, &self)) + let p = JsonPath::try_from(query)?; + Ok(p.find(&self)) } } @@ -217,8 +196,8 @@ impl JsonPathQuery for Value { impl JsonPathQuery for T where T: Deref { fn path(self, query: &str) -> Result { - let p = JsonPathInst::from_str(query)?; - Ok(find(&p, self.deref())) + let p = JsonPath::from_str(query)?; + Ok(p.find(self.deref())) } } */ @@ -233,12 +212,12 @@ impl JsonPathQuery for T /// ``` /// use std::str::FromStr; /// use serde_json::{json, Value}; -/// use jsonpath_rust::{jp_v, find_slice, JsonPathQuery, JsonPathInst, JsonPathValue}; +/// use jsonpath_rust::{jp_v, JsonPathQuery, JsonPath, JsonPathValue}; /// /// fn test() -> Result<(), Box> { /// let json: Value = serde_json::from_str("{}")?; -/// let path: JsonPathInst = JsonPathInst::from_str("$..book[?(@.author size 10)].title")?; -/// let v = find_slice(&path, &json); +/// let path = JsonPath::try_from("$..book[?(@.author size 10)].title")?; +/// let v = path.find_slice(&json); /// /// let js = json!("Sayings of the Century"); /// assert_eq!(v, jp_v![&js;"",]); @@ -257,21 +236,21 @@ macro_rules! jp_v { ($(&$v:expr;$s:expr),+ $(,)?) =>{ { - let mut res = Vec::new(); - $( - res.push(jp_v!(&$v ; $s)); - )+ - res + vec![ + $( + jp_v!(&$v ; $s), + )+ + ] } }; ($(&$v:expr),+ $(,)?) => { { - let mut res = Vec::new(); - $( - res.push(jp_v!(&$v)); - )+ - res + vec![ + $( + jp_v!(&$v), + )+ + ] } }; @@ -397,899 +376,18 @@ 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> { - use crate::path::Path; - let instance = json_path_instance(&path.inner, json); - let res = instance.find(JsonPathValue::from_root(json)); - let has_v: Vec> = res.into_iter().filter(|v| v.has_value()).collect(); - - if has_v.is_empty() { - vec![NoValue] - } else { - has_v - } -} - -/// finds a slice of data and wrap it with Value::Array by cloning the data. -/// Returns either an array of elements or Json::Null if the match is incorrect. -/// -/// In case, if there is no match `find` will return `json!(null)`. -/// -/// ## 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 cloned_data = jsonpath_rust::find(&path, &data); -/// -/// assert_eq!(cloned_data, Value::Array(vec![json!({"active":1})])); -/// ``` -pub fn find(path: &JsonPathInst, json: &Value) -> Value { - let slice = find_slice(path, json); - if !slice.is_empty() { - if JsonPathValue::only_no_value(&slice) { - Value::Null - } else { - Value::Array( - slice - .into_iter() - .filter(|v| v.has_value()) - .map(|v| v.to_data()) - .collect(), - ) - } - } else { - Value::Array(vec![]) - } -} - -/// 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) - .into_iter() - .flat_map(|v| v.to_path()) - .map(|v| v.into()) - .collect(), - ) -} - #[cfg(test)] mod tests { - use crate::JsonPathQuery; - use crate::JsonPathValue::{NoValue, Slice}; - use crate::{jp_v, JsonPathInst, JsonPathValue}; - use serde_json::{json, Value}; - use std::ops::Deref; - use std::str::FromStr; - - fn test(json: &str, path: &str, expected: Vec>) { - let json: Value = match serde_json::from_str(json) { - Ok(json) => json, - Err(e) => panic!("error while parsing json: {}", e), - }; - let path = match JsonPathInst::from_str(path) { - Ok(path) => path, - Err(e) => panic!("error while parsing jsonpath: {}", e), - }; - - assert_eq!(super::find_slice(&path, &json), expected) - } - - fn template_json<'a>() -> &'a str { - r#" {"store": { "book": [ - { - "category": "reference", - "author": "Nigel Rees", - "title": "Sayings of the Century", - "price": 8.95 - }, - { - "category": "fiction", - "author": "Evelyn Waugh", - "title": "Sword of Honour", - "price": 12.99 - }, - { - "category": "fiction", - "author": "Herman Melville", - "title": "Moby Dick", - "isbn": "0-553-21311-3", - "price": 8.99 - }, - { - "category": "fiction", - "author": "J. R. R. Tolkien", - "title": "The Lord of the Rings", - "isbn": "0-395-19395-8", - "price": 22.99 - } - ], - "bicycle": { - "color": "red", - "price": 19.95 - } - }, - "array":[0,1,2,3,4,5,6,7,8,9], - "orders":[ - { - "ref":[1,2,3], - "id":1, - "filled": true - }, - { - "ref":[4,5,6], - "id":2, - "filled": false - }, - { - "ref":[7,8,9], - "id":3, - "filled": null - } - ], - "expensive": 10 }"# - } - - #[test] - fn simple_test() { - let j1 = json!(2); - test("[1,2,3]", "$[1]", jp_v![&j1;"$[1]",]); - } - - #[test] - fn root_test() { - let js = serde_json::from_str(template_json()).unwrap(); - test(template_json(), "$", jp_v![&js;"$",]); - } - - #[test] - fn descent_test() { - let v1 = json!("reference"); - let v2 = json!("fiction"); - test( - template_json(), - "$..category", - jp_v![ - &v1;"$.['store'].['book'][0].['category']", - &v2;"$.['store'].['book'][1].['category']", - &v2;"$.['store'].['book'][2].['category']", - &v2;"$.['store'].['book'][3].['category']",], - ); - let js1 = json!(19.95); - let js2 = json!(8.95); - let js3 = json!(12.99); - let js4 = json!(8.99); - let js5 = json!(22.99); - test( - template_json(), - "$.store..price", - jp_v![ - &js1;"$.['store'].['bicycle'].['price']", - &js2;"$.['store'].['book'][0].['price']", - &js3;"$.['store'].['book'][1].['price']", - &js4;"$.['store'].['book'][2].['price']", - &js5;"$.['store'].['book'][3].['price']", - ], - ); - let js1 = json!("Nigel Rees"); - let js2 = json!("Evelyn Waugh"); - let js3 = json!("Herman Melville"); - let js4 = json!("J. R. R. Tolkien"); - test( - template_json(), - "$..author", - jp_v![ - &js1;"$.['store'].['book'][0].['author']", - &js2;"$.['store'].['book'][1].['author']", - &js3;"$.['store'].['book'][2].['author']", - &js4;"$.['store'].['book'][3].['author']",], - ); - } - - #[test] - fn wildcard_test() { - let js1 = json!("reference"); - let js2 = json!("fiction"); - test( - template_json(), - "$..book.[*].category", - jp_v![ - &js1;"$.['store'].['book'][0].['category']", - &js2;"$.['store'].['book'][1].['category']", - &js2;"$.['store'].['book'][2].['category']", - &js2;"$.['store'].['book'][3].['category']",], - ); - let js1 = json!("Nigel Rees"); - let js2 = json!("Evelyn Waugh"); - let js3 = json!("Herman Melville"); - let js4 = json!("J. R. R. Tolkien"); - test( - template_json(), - "$.store.book[*].author", - jp_v![ - &js1;"$.['store'].['book'][0].['author']", - &js2;"$.['store'].['book'][1].['author']", - &js3;"$.['store'].['book'][2].['author']", - &js4;"$.['store'].['book'][3].['author']",], - ); - } - - #[test] - fn descendent_wildcard_test() { - let js1 = json!("Moby Dick"); - let js2 = json!("The Lord of the Rings"); - test( - template_json(), - "$..*.[?(@.isbn)].title", - jp_v![ - &js1;"$.['store'].['book'][2].['title']", - &js2;"$.['store'].['book'][3].['title']", - &js1;"$.['store'].['book'][2].['title']", - &js2;"$.['store'].['book'][3].['title']"], - ); - } - - #[test] - fn field_test() { - let value = json!({"active":1}); - test( - r#"{"field":{"field":[{"active":1},{"passive":1}]}}"#, - "$.field.field[?(@.active)]", - jp_v![&value;"$.['field'].['field'][0]",], - ); - } - - #[test] - fn index_index_test() { - let value = json!("0-553-21311-3"); - test( - template_json(), - "$..book[2].isbn", - jp_v![&value;"$.['store'].['book'][2].['isbn']",], - ); - } - - #[test] - fn index_unit_index_test() { - let value = json!("0-553-21311-3"); - test( - template_json(), - "$..book[2,4].isbn", - jp_v![&value;"$.['store'].['book'][2].['isbn']",], - ); - let value1 = json!("0-395-19395-8"); - test( - template_json(), - "$..book[2,3].isbn", - jp_v![&value;"$.['store'].['book'][2].['isbn']", &value1;"$.['store'].['book'][3].['isbn']",], - ); - } - - #[test] - fn index_unit_keys_test() { - let js1 = json!("Moby Dick"); - let js2 = json!(8.99); - let js3 = json!("The Lord of the Rings"); - let js4 = json!(22.99); - test( - template_json(), - "$..book[2,3]['title','price']", - jp_v![ - &js1;"$.['store'].['book'][2].['title']", - &js2;"$.['store'].['book'][2].['price']", - &js3;"$.['store'].['book'][3].['title']", - &js4;"$.['store'].['book'][3].['price']",], - ); - } - - #[test] - fn index_slice_test() { - let i0 = "$.['array'][0]"; - let i1 = "$.['array'][1]"; - let i2 = "$.['array'][2]"; - let i3 = "$.['array'][3]"; - let i4 = "$.['array'][4]"; - let i5 = "$.['array'][5]"; - let i6 = "$.['array'][6]"; - let i7 = "$.['array'][7]"; - let i8 = "$.['array'][8]"; - let i9 = "$.['array'][9]"; - - let j0 = json!(0); - let j1 = json!(1); - let j2 = json!(2); - let j3 = json!(3); - let j4 = json!(4); - let j5 = json!(5); - let j6 = json!(6); - let j7 = json!(7); - let j8 = json!(8); - let j9 = json!(9); - test( - template_json(), - "$.array[:]", - jp_v![ - &j0;&i0, - &j1;&i1, - &j2;&i2, - &j3;&i3, - &j4;&i4, - &j5;&i5, - &j6;&i6, - &j7;&i7, - &j8;&i8, - &j9;&i9,], - ); - test(template_json(), "$.array[1:4:2]", jp_v![&j1;&i1, &j3;&i3,]); - test( - template_json(), - "$.array[::3]", - jp_v![&j0;&i0, &j3;&i3, &j6;&i6, &j9;&i9,], - ); - test(template_json(), "$.array[-1:]", jp_v![&j9;&i9,]); - test(template_json(), "$.array[-2:-1]", jp_v![&j8;&i8,]); - } - - #[test] - fn index_filter_test() { - let moby = json!("Moby Dick"); - let rings = json!("The Lord of the Rings"); - test( - template_json(), - "$..book[?(@.isbn)].title", - jp_v![ - &moby;"$.['store'].['book'][2].['title']", - &rings;"$.['store'].['book'][3].['title']",], - ); - let sword = json!("Sword of Honour"); - test( - template_json(), - "$..book[?(@.price != 8.95)].title", - jp_v![ - &sword;"$.['store'].['book'][1].['title']", - &moby;"$.['store'].['book'][2].['title']", - &rings;"$.['store'].['book'][3].['title']",], - ); - let sayings = json!("Sayings of the Century"); - test( - template_json(), - "$..book[?(@.price == 8.95)].title", - jp_v![&sayings;"$.['store'].['book'][0].['title']",], - ); - let js895 = json!(8.95); - test( - template_json(), - "$..book[?(@.author ~= '.*Rees')].price", - jp_v![&js895;"$.['store'].['book'][0].['price']",], - ); - let js12 = json!(12.99); - let js899 = json!(8.99); - let js2299 = json!(22.99); - test( - template_json(), - "$..book[?(@.price >= 8.99)].price", - jp_v![ - &js12;"$.['store'].['book'][1].['price']", - &js899;"$.['store'].['book'][2].['price']", - &js2299;"$.['store'].['book'][3].['price']", - ], - ); - test( - template_json(), - "$..book[?(@.price > 8.99)].price", - jp_v![ - &js12;"$.['store'].['book'][1].['price']", - &js2299;"$.['store'].['book'][3].['price']",], - ); - test( - template_json(), - "$..book[?(@.price < 8.99)].price", - jp_v![&js895;"$.['store'].['book'][0].['price']",], - ); - test( - template_json(), - "$..book[?(@.price <= 8.99)].price", - jp_v![ - &js895;"$.['store'].['book'][0].['price']", - &js899;"$.['store'].['book'][2].['price']", - ], - ); - test( - template_json(), - "$..book[?(@.price <= $.expensive)].price", - jp_v![ - &js895;"$.['store'].['book'][0].['price']", - &js899;"$.['store'].['book'][2].['price']", - ], - ); - test( - template_json(), - "$..book[?(@.price >= $.expensive)].price", - jp_v![ - &js12;"$.['store'].['book'][1].['price']", - &js2299;"$.['store'].['book'][3].['price']", - ], - ); - test( - template_json(), - "$..book[?(@.title in ['Moby Dick','Shmoby Dick','Big Dick','Dicks'])].price", - jp_v![&js899;"$.['store'].['book'][2].['price']",], - ); - test( - template_json(), - "$..book[?(@.title nin ['Moby Dick','Shmoby Dick','Big Dick','Dicks'])].title", - jp_v![ - &sayings;"$.['store'].['book'][0].['title']", - &sword;"$.['store'].['book'][1].['title']", - &rings;"$.['store'].['book'][3].['title']",], - ); - test( - template_json(), - "$..book[?(@.author size 10)].title", - jp_v![&sayings;"$.['store'].['book'][0].['title']",], - ); - let filled_true = json!(1); - test( - template_json(), - "$.orders[?(@.filled == true)].id", - jp_v![&filled_true;"$.['orders'][0].['id']",], - ); - let filled_null = json!(3); - test( - template_json(), - "$.orders[?(@.filled == null)].id", - jp_v![&filled_null;"$.['orders'][2].['id']",], - ); - } - - #[test] - fn index_filter_sets_test() { - let j1 = json!(1); - test( - template_json(), - "$.orders[?(@.ref subsetOf [1,2,3,4])].id", - jp_v![&j1;"$.['orders'][0].['id']",], - ); - let j2 = json!(2); - test( - template_json(), - "$.orders[?(@.ref anyOf [1,4])].id", - jp_v![&j1;"$.['orders'][0].['id']", &j2;"$.['orders'][1].['id']",], - ); - let j3 = json!(3); - test( - template_json(), - "$.orders[?(@.ref noneOf [3,6])].id", - jp_v![&j3;"$.['orders'][2].['id']",], - ); - } - - #[test] - fn query_test() { - let json: Box = serde_json::from_str(template_json()).expect("to get json"); - let v = json - .path("$..book[?(@.author size 10)].title") - .expect("the path is correct"); - assert_eq!(v, json!(["Sayings of the Century"])); - - let json: Value = serde_json::from_str(template_json()).expect("to get json"); - let path = &json - .path("$..book[?(@.author size 10)].title") - .expect("the path is correct"); - - assert_eq!(path, &json!(["Sayings of the Century"])); - } - - #[test] - fn find_slice_test() { - let json: Box = serde_json::from_str(template_json()).expect("to get json"); - let path: Box = Box::from( - JsonPathInst::from_str("$..book[?(@.author size 10)].title") - .expect("the path is correct"), - ); - let v = super::find_slice(&path, &json); - let js = json!("Sayings of the Century"); - assert_eq!(v, jp_v![&js;"$.['store'].['book'][0].['title']",]); - } - - #[test] - fn find_in_array_test() { - let json: Box = Box::new(json!([{"verb": "TEST"}, {"verb": "RUN"}])); - let path: Box = Box::from( - JsonPathInst::from_str("$.[?(@.verb == 'TEST')]").expect("the path is correct"), - ); - let v = super::find_slice(&path, &json); - let js = json!({"verb":"TEST"}); - assert_eq!(v, jp_v![&js;"$[0]",]); - } - - #[test] - fn length_test() { - let json: Box = - Box::new(json!([{"verb": "TEST"},{"verb": "TEST"}, {"verb": "RUN"}])); - let path: Box = Box::from( - JsonPathInst::from_str("$.[?(@.verb == 'TEST')].length()") - .expect("the path is correct"), - ); - let v = super::find(&path, &json); - let js = json!([2]); - assert_eq!(v, js); - - let json: Box = - Box::new(json!([{"verb": "TEST"},{"verb": "TEST"}, {"verb": "RUN"}])); - let path: Box = - Box::from(JsonPathInst::from_str("$.length()").expect("the path is correct")); - assert_eq!(super::find(&path, &json), json!([3])); - - // length of search following the wildcard returns correct result - let json: Box = - Box::new(json!([{"verb": "TEST"},{"verb": "TEST","x":3}, {"verb": "RUN"}])); - let path: Box = Box::from( - JsonPathInst::from_str("$.[?(@.verb == 'TEST')].[*].length()") - .expect("the path is correct"), - ); - assert_eq!(super::find(&path, &json), json!([3])); - - // length of object returns 0 - let json: Box = Box::new(json!({"verb": "TEST"})); - let path: Box = - Box::from(JsonPathInst::from_str("$.length()").expect("the path is correct")); - assert_eq!(super::find(&path, &json), Value::Null); - - // length of integer returns null - let json: Box = Box::new(json!(1)); - let path: Box = - Box::from(JsonPathInst::from_str("$.length()").expect("the path is correct")); - assert_eq!(super::find(&path, &json), Value::Null); - - // length of array returns correct result - let json: Box = Box::new(json!([[1], [2], [3]])); - let path: Box = - Box::from(JsonPathInst::from_str("$.length()").expect("the path is correct")); - assert_eq!(super::find(&path, &json), json!([3])); - - // path does not exist returns length null - let json: Box = - Box::new(json!([{"verb": "TEST"},{"verb": "TEST"}, {"verb": "RUN"}])); - let path: Box = - Box::from(JsonPathInst::from_str("$.not.exist.length()").expect("the path is correct")); - assert_eq!(super::find(&path, &json), Value::Null); - - // seraching one value returns correct length - let json: Box = - Box::new(json!([{"verb": "TEST"},{"verb": "TEST"}, {"verb": "RUN"}])); - let path: Box = Box::from( - JsonPathInst::from_str("$.[?(@.verb == 'RUN')].length()").expect("the path is correct"), - ); - - let v = super::find(&path, &json); - let js = json!([1]); - assert_eq!(v, js); - - // searching correct path following unexisting key returns length 0 - let json: Box = - Box::new(json!([{"verb": "TEST"},{"verb": "TEST"}, {"verb": "RUN"}])); - let path: Box = Box::from( - JsonPathInst::from_str("$.[?(@.verb == 'RUN')].key123.length()") - .expect("the path is correct"), - ); - - let v = super::find(&path, &json); - let js = json!(null); - assert_eq!(v, js); - - // fetching first object returns length null - let json: Box = - Box::new(json!([{"verb": "TEST"},{"verb": "TEST"}, {"verb": "RUN"}])); - let path: Box = - Box::from(JsonPathInst::from_str("$.[0].length()").expect("the path is correct")); - - let v = super::find(&path, &json); - let js = Value::Null; - assert_eq!(v, js); - - // length on fetching the index after search gives length of the object (array) - let json: Box = Box::new(json!([{"prop": [["a", "b", "c"], "d"]}])); - let path: Box = Box::from( - JsonPathInst::from_str("$.[?(@.prop)].prop.[0].length()").expect("the path is correct"), - ); - - let v = super::find(&path, &json); - let js = json!([3]); - assert_eq!(v, js); - - // length on fetching the index after search gives length of the object (string) - let json: Box = Box::new(json!([{"prop": [["a", "b", "c"], "d"]}])); - let path: Box = Box::from( - JsonPathInst::from_str("$.[?(@.prop)].prop.[1].length()").expect("the path is correct"), - ); - - let v = super::find(&path, &json); - let js = Value::Null; - assert_eq!(v, js); - } - - #[test] - fn no_value_index_from_not_arr_filter_test() { - let json: Box = Box::new(json!({ - "field":"field", - })); - - let path: Box = - Box::from(JsonPathInst::from_str("$.field[1]").expect("the path is correct")); - let v = super::find_slice(&path, &json); - assert_eq!(v, vec![NoValue]); - - let json: Box = Box::new(json!({ - "field":[0], - })); - - let path: Box = - Box::from(JsonPathInst::from_str("$.field[1]").expect("the path is correct")); - let v = super::find_slice(&path, &json); - assert_eq!(v, vec![NoValue]); - } - - #[test] - fn no_value_filter_from_not_arr_filter_test() { - let json: Box = Box::new(json!({ - "field":"field", - })); - - let path: Box = - Box::from(JsonPathInst::from_str("$.field[?(@ == 0)]").expect("the path is correct")); - let v = super::find_slice(&path, &json); - assert_eq!(v, vec![NoValue]); - } - - #[test] - fn no_value_index_filter_test() { - let json: Box = Box::new(json!({ - "field":[{"f":1},{"f":0}], - })); - - let path: Box = Box::from( - JsonPathInst::from_str("$.field[?(@.f_ == 0)]").expect("the path is correct"), - ); - let v = super::find_slice(&path, &json); - assert_eq!(v, vec![NoValue]); - } - - #[test] - fn no_value_decent_test() { - let json: Box = Box::new(json!({ - "field":[{"f":1},{"f":{"f_":1}}], - })); - - let path: Box = - Box::from(JsonPathInst::from_str("$..f_").expect("the path is correct")); - let v = super::find_slice(&path, &json); - assert_eq!( - v, - vec![Slice(&json!(1), "$.['field'][1].['f'].['f_']".to_string())] - ); - } - - #[test] - fn no_value_chain_test() { - let json: Box = Box::new(json!({ - "field":{"field":[1]}, - })); - - let path: Box = - Box::from(JsonPathInst::from_str("$.field_.field").expect("the path is correct")); - let v = super::find_slice(&path, &json); - assert_eq!(v, vec![NoValue]); - - let path: Box = Box::from( - JsonPathInst::from_str("$.field_.field[?(@ == 1)]").expect("the path is correct"), - ); - let v = super::find_slice(&path, &json); - assert_eq!(v, vec![NoValue]); - } - - #[test] - fn no_value_filter_test() { - // searching unexisting value returns length 0 - let json: Box = - Box::new(json!([{"verb": "TEST"},{"verb": "TEST"}, {"verb": "RUN"}])); - let path: Box = Box::from( - JsonPathInst::from_str("$.[?(@.verb == \"RUN1\")]").expect("the path is correct"), - ); - let v = super::find(&path, &json); - let js = json!(null); - assert_eq!(v, js); - } - - #[test] - fn no_value_len_test() { - let json: Box = Box::new(json!({ - "field":{"field":1}, - })); - - let path: Box = Box::from( - JsonPathInst::from_str("$.field.field.length()").expect("the path is correct"), - ); - let v = super::find_slice(&path, &json); - assert_eq!(v, vec![NoValue]); - - let json: Box = Box::new(json!({ - "field":[{"a":1},{"a":1}], - })); - let path: Box = Box::from( - JsonPathInst::from_str("$.field[?(@.a == 0)].f.length()").expect("the path is correct"), - ); - let v = super::find_slice(&path, &json); - assert_eq!(v, vec![NoValue]); - } - - #[test] - fn no_clone_api_test() { - fn test_coercion(value: &Value) -> Value { - value.clone() - } - - let json: Value = serde_json::from_str(template_json()).expect("to get json"); - let query = JsonPathInst::from_str("$..book[?(@.author size 10)].title") - .expect("the path is correct"); - - let results = query.find_slice(&json); - let v = results.first().expect("to get value"); - - // V can be implicitly converted to &Value - test_coercion(v); - - // To explicitly convert to &Value, use deref() - assert_eq!(v.deref(), &json!("Sayings of the Century")); - } - - #[test] - fn logical_exp_test() { - let json: Box = Box::new(json!({"first":{"second":[{"active":1},{"passive":1}]}})); - - let path: Box = Box::from( - JsonPathInst::from_str("$.first[?(@.does_not_exist && @.does_not_exist >= 1.0)]") - .expect("the path is correct"), - ); - let v = super::find_slice(&path, &json); - assert_eq!(v, vec![NoValue]); - - let path: Box = Box::from( - JsonPathInst::from_str("$.first[?(@.does_not_exist >= 1.0)]") - .expect("the path is correct"), - ); - let v = super::find_slice(&path, &json); - assert_eq!(v, vec![NoValue]); - } - - #[test] - fn regex_filter_test() { - let json: Box = Box::new(json!({ - "author":"abcd(Rees)", - })); - - let path: Box = Box::from( - JsonPathInst::from_str("$.[?(@.author ~= '(?i)d\\(Rees\\)')]") - .expect("the path is correct"), - ); - assert_eq!( - super::find_slice(&path, &json.clone()), - vec![Slice(&json!({"author":"abcd(Rees)"}), "$".to_string())] - ); - } - - #[test] - fn logical_not_exp_test() { - let json: Box = Box::new(json!({"first":{"second":{"active":1}}})); - let path: Box = Box::from( - JsonPathInst::from_str("$.first[?(!@.does_not_exist >= 1.0)]") - .expect("the path is correct"), - ); - let v = super::find_slice(&path, &json); - assert_eq!( - v, - vec![Slice( - &json!({"second":{"active": 1}}), - "$.['first']".to_string() - )] - ); - - let path: Box = Box::from( - JsonPathInst::from_str("$.first[?(!(@.does_not_exist >= 1.0))]") - .expect("the path is correct"), - ); - let v = super::find_slice(&path, &json); - assert_eq!( - v, - vec![Slice( - &json!({"second":{"active": 1}}), - "$.['first']".to_string() - )] - ); - - let path: Box = Box::from( - JsonPathInst::from_str("$.first[?(!(@.second.active == 1) || @.second.active == 1)]") - .expect("the path is correct"), - ); - let v = super::find_slice(&path, &json); - assert_eq!( - v, - vec![Slice( - &json!({"second":{"active": 1}}), - "$.['first']".to_string() - )] - ); - - let path: Box = Box::from( - JsonPathInst::from_str("$.first[?(!@.second.active == 1 && !@.second.active == 1 || !@.second.active == 2)]") - .expect("the path is correct"), - ); - let v = super::find_slice(&path, &json); - assert_eq!( - v, - vec![Slice( - &json!({"second":{"active": 1}}), - "$.['first']".to_string() - )] - ); - } - // #[test] // fn no_value_len_field_test() { // let json: Box = // Box::new(json!([{"verb": "TEST","a":[1,2,3]},{"verb": "TEST","a":[1,2,3]},{"verb": "TEST"}, {"verb": "RUN"}])); - // let path: Box = Box::from( - // JsonPathInst::from_str("$.[?(@.verb == 'TEST')].a.length()") + // let path: Box = Box::from( + // JsonPath::from_str("$.[?(@.verb == 'TEST')].a.length()") // .expect("the path is correct"), // ); - // let finder = JsonPathFinder::new(json, path); // - // let v = finder.find_slice(); + // let v = json.find_slice(&path); // assert_eq!(v, vec![NewValue(json!(3))]); // } } diff --git a/src/parser/macros.rs b/src/parser/macros.rs index e884700..284d04b 100644 --- a/src/parser/macros.rs +++ b/src/parser/macros.rs @@ -7,6 +7,7 @@ macro_rules! filter { ( $left:expr,||, $right:expr) => {FilterExpression::Or(Box::new($left),Box::new($right)) }; ( $left:expr,&&, $right:expr) => {FilterExpression::And(Box::new($left),Box::new($right)) }; } + #[macro_export] macro_rules! op { ( ) => { @@ -27,14 +28,20 @@ macro_rules! op { macro_rules! idx { ( $s:literal) => {JsonPathIndex::Single(json!($s))}; ( idx $($ss:literal),+) => {{ - let mut ss_vec = Vec::new(); - $( ss_vec.push(json!($ss)) ; )+ - JsonPathIndex::UnionIndex(ss_vec) + let ss_vec = vec![ + $( + json!($ss), + )+ + ]; + JsonPathIndex::UnionIndex(ss_vec) }}; ( $($ss:literal),+) => {{ - let mut ss_vec = Vec::new(); - $( ss_vec.push($ss.to_string()) ; )+ - JsonPathIndex::UnionKeys(ss_vec) + let ss_vec = vec![ + $( + $ss.to_string(), + )+ + ]; + JsonPathIndex::UnionKeys(ss_vec) }}; ( $s:literal) => {JsonPathIndex::Single(json!($s))}; ( ? $s:expr) => {JsonPathIndex::Filter($s)}; @@ -51,12 +58,25 @@ macro_rules! idx { #[macro_export] macro_rules! chain { ($($ss:expr),+) => {{ - let mut ss_vec = Vec::new(); - $( ss_vec.push($ss) ; )+ + let ss_vec = vec![ + $( + $ss, + )+ + ]; JsonPath::Chain(ss_vec) }}; } +/// Can be used to Parse a JsonPath with a more native way. +/// e.g. +/// ```rust +/// use jsonpath_rust::{path, JsonPath}; +/// use std::str::FromStr; +/// +/// let path = JsonPath::from_str(".abc.*").unwrap(); +/// let path2 = JsonPath::Chain(vec![path!("abc"), path!(*)]); +/// assert_eq!(path, path2); +/// ``` #[macro_export] macro_rules! path { ( ) => {JsonPath::Empty}; @@ -65,19 +85,25 @@ macro_rules! path { (@) => {JsonPath::Current(Box::new(JsonPath::Empty))}; (@$e:expr) => {JsonPath::Current(Box::new($e))}; (@,$($ss:expr),+) => {{ - let mut ss_vec = Vec::new(); - $( ss_vec.push($ss) ; )+ - let chain = JsonPath::Chain(ss_vec); - JsonPath::Current(Box::new(chain)) + let ss_vec = vec![ + $( + $ss, + )+ + ]; + let chain = JsonPath::Chain(ss_vec); + JsonPath::Current(Box::new(chain)) }}; (..$e:literal) => {JsonPath::Descent($e.to_string())}; (..*) => {JsonPath::DescentW}; ($e:literal) => {JsonPath::Field($e.to_string())}; ($e:expr) => {JsonPath::Index($e)}; } -#[macro_export] -macro_rules! function { - (length) => { - JsonPath::Fn(Function::Length) - }; -} + +#[cfg(test)] +pub(crate) use chain; +#[cfg(test)] +pub(crate) use filter; +#[cfg(test)] +pub(crate) use idx; +#[cfg(test)] +pub(crate) use op; diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 443c7ba..65cacde 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -1,9 +1,12 @@ //! The parser for the jsonpath. //! The module grammar denotes the structure of the parsing grammar -pub mod errors; -mod macros; -pub mod model; +mod errors; +pub(crate) mod macros; +pub(crate) mod model; #[allow(clippy::module_inception)] -#[allow(clippy::result_large_err)] -pub mod parser; +pub(crate) mod parser; + +pub use errors::JsonPathParserError; +pub use model::JsonPath; +pub use parser::{parse_json_path, Rule}; diff --git a/src/parser/model.rs b/src/parser/model.rs index 8f386f4..90da1ad 100644 --- a/src/parser/model.rs +++ b/src/parser/model.rs @@ -1,11 +1,13 @@ -use crate::parse_json_path; +use super::parse_json_path; use serde_json::Value; -use std::convert::TryFrom; +use std::{convert::TryFrom, str::FromStr}; use super::errors::JsonPathParserError; /// The basic structures for parsing json paths. /// The common logic of the structures pursues to correspond the internal parsing structure. +/// +/// usually it's created by using [`FromStr`] or [`TryFrom<&str>`] #[derive(Debug, Clone)] pub enum JsonPath { /// The $ operator @@ -30,12 +32,6 @@ pub enum JsonPath { Fn(Function), } -impl JsonPath { - pub fn current(jp: JsonPath) -> Self { - JsonPath::Current(Box::new(jp)) - } -} - impl TryFrom<&str> for JsonPath { type Error = JsonPathParserError; @@ -49,6 +45,19 @@ impl TryFrom<&str> for JsonPath { } } +impl FromStr for JsonPath { + type Err = JsonPathParserError; + + /// Parses a string into a [JsonPath]. + /// + /// # Errors + /// + /// Returns a variant of [JsonPathParserError] if the parsing operation failed. + fn from_str(value: &str) -> Result { + parse_json_path(value) + } +} + #[derive(Debug, PartialEq, Clone)] pub enum Function { /// length() diff --git a/src/parser/parser.rs b/src/parser/parser.rs index aa804a3..d99f8f9 100644 --- a/src/parser/parser.rs +++ b/src/parser/parser.rs @@ -43,7 +43,8 @@ fn parse_internal(rule: Pair) -> Result { .next() .map(parse_internal) .unwrap_or(Ok(JsonPath::Empty)) - .map(JsonPath::current), + .map(Box::new) + .map(JsonPath::Current), Rule::chain => rule .into_inner() .map(parse_internal) @@ -273,7 +274,8 @@ fn down(rule: Pair) -> Result, JsonPathParserError> { #[cfg(test)] mod tests { use super::*; - use crate::{chain, filter, function, idx, op, path}; + use crate::parser::macros::{chain, filter, idx, op}; + use crate::path; use serde_json::json; use std::panic; @@ -543,7 +545,7 @@ mod tests { fn fn_size_test() { test( "$.k.length()", - vec![path!($), path!("k"), function!(length)], + vec![path!($), path!("k"), JsonPath::Fn(Function::Length)], ); test( diff --git a/src/path/index.rs b/src/path/index.rs index c0f7977..cbb00ea 100644 --- a/src/path/index.rs +++ b/src/path/index.rs @@ -355,12 +355,15 @@ impl<'a> Path<'a> for FilterPath<'a> { #[cfg(test)] mod tests { + use crate::jp_v; + use crate::parser::macros::{chain, filter, idx, op}; use crate::parser::model::{FilterExpression, FilterSign, JsonPath, JsonPathIndex, Operand}; use crate::path::index::{ArrayIndex, ArraySlice}; use crate::path::JsonPathValue; use crate::path::{json_path_instance, Path}; use crate::JsonPathValue::NoValue; - use crate::{chain, filter, idx, jp_v, op, path}; + + use crate::path; use serde_json::json; #[test] diff --git a/src/path/top.rs b/src/path/top.rs index 54c9b34..770cd1f 100644 --- a/src/path/top.rs +++ b/src/path/top.rs @@ -326,11 +326,13 @@ impl<'a> Path<'a> for Chain<'a> { #[cfg(test)] mod tests { + use crate::jp_v; + use crate::parser::macros::{chain, idx}; use crate::parser::model::{JsonPath, JsonPathIndex}; + use crate::path; use crate::path::top::{deep_flatten, json_path_instance, Function, ObjectField, RootPointer}; use crate::path::{JsonPathValue, Path}; use crate::JsonPathValue::NoValue; - use crate::{chain, function, idx, jp_v, path}; use serde_json::json; use serde_json::Value; @@ -613,7 +615,7 @@ mod tests { "key3": {} }); - let chain = chain!(path!($), path!(*), function!(length)); + let chain = chain!(path!($), path!(*), JsonPath::Fn(Function::Length)); let path_inst = json_path_instance(&chain, &json); assert_eq!( @@ -621,7 +623,7 @@ mod tests { vec![jp_v!(json!(3))] ); - let chain = chain!(path!($), path!("key1"), function!(length)); + let chain = chain!(path!($), path!("key1"), JsonPath::Fn(Function::Length)); let path_inst = json_path_instance(&chain, &json); assert_eq!( path_inst.flat_find(vec![jp_v!(&json)], false),