diff --git a/README.md b/README.md index 354369a..8aaa4fd 100644 --- a/README.md +++ b/README.md @@ -17,12 +17,15 @@ It is JsonPath [JsonPath](https://goessner.net/articles/JsonPath/) engine writte - [jsonpath_lib crate](#jsonpath_lib-crate) - [Rust - jsonpath::Selector struct](#rust---jsonpathselector-struct) +- [Rust - jsonpath::SelectorMut struct](#rust---jsonpathselectormut-struct) - [Rust - jsonpath::select(json: &serde_json::value::Value, jsonpath: &str)](#rust---jsonpathselectjson-serde_jsonvaluevalue-jsonpath-str) - [Rust - jsonpath::select_as_str(json_str: &str, jsonpath: &str)](#rust---jsonpathselect_as_strjson-str-jsonpath-str) - [Rust - jsonpath::select_as\(json_str: &str, jsonpath: &str)](#rust---jsonpathselect_ast-serdededeserializeownedjson-str-jsonpath-str) - [Rust - jsonpath::compile(jsonpath: &str)](#rust---jsonpathcompilejsonpath-str) - [Rust - jsonpath::selector(json: &serde_json::value::Value)](#rust---jsonpathselectorjson-serde_jsonvaluevalue) - [Rust - jsonpath::selector_as\(json: &serde_json::value::Value)](#rust---jsonpathselector_ast-serdededeserializeownedjson-serde_jsonvaluevalue) +- [Rust - jsonpath::delete(value: &Value, path: &str)](#rust---jsonpathdeletevalue-value-path-str) +- [Rust - jsonpath::replace_with\ Value`\>(value: &Value, path: &str, fun: &mut F)](#rust---jsonpathreplace_withf-fnmutvalue---valuevalue-value-path-str-fun-mut-f) - [Rust - Other Examples](https://github.com/freestrings/jsonpath/wiki/rust-examples) ## Javascript API @@ -84,6 +87,50 @@ let result = selector.select_as::().unwrap(); assert_eq!(vec![Friend { name: "친구3".to_string(), age: Some(30) }], result); ``` +#### Rust - jsonpath::SelectorMut struct + +```rust +let json_obj = json!({ + "school": { + "friends": [ + {"name": "친구1", "age": 20}, + {"name": "친구2", "age": 20} + ] + }, + "friends": [ + {"name": "친구3", "age": 30}, + {"name": "친구4"} +]}); + +let mut selector_mut = SelectorMut::new(); + +let result = selector_mut + .str_path("$..[?(@.age == 20)].age").unwrap() + .value(json_obj) + .replace_with(&mut |v| { + let age = if let Value::Number(n) = v { + n.as_u64().unwrap() * 2 + } else { + 0 + }; + + json!(age) + }).unwrap() + .take().unwrap(); + +assert_eq!(result, json!({ + "school": { + "friends": [ + {"name": "친구1", "age": 40}, + {"name": "친구2", "age": 40} + ] + }, + "friends": [ + {"name": "친구3", "age": 30}, + {"name": "친구4"} +]})); +``` + #### Rust - jsonpath::select(json: &serde_json::value::Value, jsonpath: &str) ```rust @@ -259,6 +306,73 @@ let ret = vec!( assert_eq!(json, ret); ``` +#### Rust - jsonpath::delete(value: &Value, path: &str) + +```rust +let json_obj = json!({ + "school": { + "friends": [ + {"name": "친구1", "age": 20}, + {"name": "친구2", "age": 20} + ] + }, + "friends": [ + {"name": "친구3", "age": 30}, + {"name": "친구4"} +]}); + +let ret = jsonpath::delete(json_obj, "$..[?(20 == @.age)]").unwrap(); + +assert_eq!(ret, json!({ + "school": { + "friends": [ + null, + null + ] + }, + "friends": [ + {"name": "친구3", "age": 30}, + {"name": "친구4"} +]})); +``` + +#### Rust - jsonpath::replace_with\ Value`\>(value: &Value, path: &str, fun: &mut F) + +```rust +let json_obj = json!({ + "school": { + "friends": [ + {"name": "친구1", "age": 20}, + {"name": "친구2", "age": 20} + ] + }, + "friends": [ + {"name": "친구3", "age": 30}, + {"name": "친구4"} +]}); + +let ret = jsonpath::replace_with(json_obj, "$..[?(@.age == 20)].age", &mut |v| { + let age = if let Value::Number(n) = v { + n.as_u64().unwrap() * 2 + } else { + 0 + }; + + json!(age) +}).unwrap(); + +assert_eq!(ret, json!({ + "school": { + "friends": [ + {"name": "친구1", "age": 40}, + {"name": "친구2", "age": 40} + ] + }, + "friends": [ + {"name": "친구3", "age": 30}, + {"name": "친구4"} +]})); +``` --- ### Javascript API diff --git a/src/lib.rs b/src/lib.rs index 236a3f9..cc9c12d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -125,11 +125,11 @@ extern crate array_tool; extern crate core; extern crate env_logger; +extern crate indexmap; #[macro_use] extern crate log; extern crate serde; extern crate serde_json; -extern crate indexmap; use serde_json::Value; @@ -142,13 +142,13 @@ mod parser; #[doc(hidden)] mod select; -/// It is a high-order function. it compile a JsonPath and then returns a function. this return-function can be reused for different JsonObjects. +/// It is a high-order function. it compile a jsonpath and then returns a closure that has JSON as argument. if you need to reuse a jsonpath, it is good for performance. /// /// ```rust /// extern crate jsonpath_lib as jsonpath; /// #[macro_use] extern crate serde_json; /// -/// let mut template = jsonpath::compile("$..friends[0]"); +/// let mut first_firend = jsonpath::compile("$..friends[0]"); /// /// let json_obj = json!({ /// "school": { @@ -162,7 +162,7 @@ mod select; /// {"name": "친구4"} /// ]}); /// -/// let json = template(&json_obj).unwrap(); +/// let json = first_firend(&json_obj).unwrap(); /// /// assert_eq!(json, vec![ /// &json!({"name": "친구3", "age": 30}), @@ -172,16 +172,20 @@ mod select; pub fn compile(path: &str) -> impl FnMut(&Value) -> Result, JsonPathError> { let node = Parser::compile(path); move |json| { - let mut selector = Selector::new(); match &node { - Ok(node) => selector.compiled_path(node.clone()), - Err(e) => return Err(JsonPathError::Path(e.clone())) - }; - selector.value(json).select() + Ok(node) => { + let mut selector = Selector::new(); + // + // TODO remove node.clone() + // + selector.compiled_path(node.clone()).value(json).select() + } + Err(e) => Err(JsonPathError::Path(e.clone())) + } } } -/// It is a high-order function that return a function. this return-function has a jsonpath as argument and return a serde_json::value::Value. so you can use different JsonPath for one JsonObject. +/// It is a high-order function. it returns a closure that has a jsonpath string as argument. you can use diffenent jsonpath for one JSON object. /// /// ```rust /// extern crate jsonpath_lib as jsonpath; @@ -223,7 +227,7 @@ pub fn selector<'a>(json: &'a Value) -> impl FnMut(&'a str) -> Result(json: &Value) -> impl FnMut(& } } -/// This function compile a jsonpath everytime and it convert `serde_json's Value` to `jsonpath's RefValue` everytime and then it return a `serde_json::value::Value`. +/// It is a simple select function. but it compile the jsonpath argument every time. /// /// ```rust /// extern crate jsonpath_lib as jsonpath; @@ -306,7 +310,7 @@ pub fn select<'a>(json: &'a Value, path: &'a str) -> Result, Json Selector::new().str_path(path)?.value(json).select() } -/// This function compile a jsonpath everytime and it convert `&str` to `jsonpath's RefValue` everytime and then it return a json string. +/// It is the same to `select` function but it return the result as string. /// /// ```rust /// extern crate jsonpath_lib as jsonpath; @@ -335,7 +339,7 @@ pub fn select_as_str(json_str: &str, path: &str) -> Result Result(json_str: &str, path: &str) -> Result, JsonPathError> { let json = serde_json::from_str(json_str).map_err(|e| JsonPathError::Serde(e.to_string()))?; Selector::new().str_path(path)?.value(&json).select_as() +} + +/// Delete(= replace with null) the JSON property using the jsonpath. +/// +/// ```rust +/// extern crate jsonpath_lib as jsonpath; +/// #[macro_use] extern crate serde_json; +/// +/// let json_obj = json!({ +/// "school": { +/// "friends": [ +/// {"name": "친구1", "age": 20}, +/// {"name": "친구2", "age": 20} +/// ] +/// }, +/// "friends": [ +/// {"name": "친구3", "age": 30}, +/// {"name": "친구4"} +/// ]}); +/// +/// let ret = jsonpath::delete(json_obj, "$..[?(20 == @.age)]").unwrap(); +/// +/// assert_eq!(ret, json!({ +/// "school": { +/// "friends": [ +/// null, +/// null +/// ] +/// }, +/// "friends": [ +/// {"name": "친구3", "age": 30}, +/// {"name": "친구4"} +/// ]})); +/// ``` +pub fn delete(value: Value, path: &str) -> Result { + let mut selector = SelectorMut::new(); + let ret = selector.str_path(path)?.value(value).delete()?.take().unwrap_or(Value::Null); + Ok(ret) +} + +/// Select JSON properties using a jsonpath and transform the result and then replace it. via closure that implements `FnMut` you can transform the selected results. +/// +/// ```rust +/// extern crate jsonpath_lib as jsonpath; +/// #[macro_use] extern crate serde_json; +/// +/// use serde_json::Value; +/// +/// let json_obj = json!({ +/// "school": { +/// "friends": [ +/// {"name": "친구1", "age": 20}, +/// {"name": "친구2", "age": 20} +/// ] +/// }, +/// "friends": [ +/// {"name": "친구3", "age": 30}, +/// {"name": "친구4"} +/// ]}); +/// +/// let ret = jsonpath::replace_with(json_obj, "$..[?(@.age == 20)].age", &mut |v| { +/// let age = if let Value::Number(n) = v { +/// n.as_u64().unwrap() * 2 +/// } else { +/// 0 +/// }; +/// +/// json!(age) +/// }).unwrap(); +/// +/// assert_eq!(ret, json!({ +/// "school": { +/// "friends": [ +/// {"name": "친구1", "age": 40}, +/// {"name": "친구2", "age": 40} +/// ] +/// }, +/// "friends": [ +/// {"name": "친구3", "age": 30}, +/// {"name": "친구4"} +/// ]})); +/// ``` +pub fn replace_with(value: Value, path: &str, fun: &mut F) -> Result + where F: FnMut(&Value) -> Value +{ + let mut selector = SelectorMut::new(); + let ret = selector.str_path(path)?.value(value).replace_with(fun)?.take().unwrap_or(Value::Null); + Ok(ret) } \ No newline at end of file diff --git a/src/select/mod.rs b/src/select/mod.rs index 1888054..af57cb5 100644 --- a/src/select/mod.rs +++ b/src/select/mod.rs @@ -988,6 +988,11 @@ impl SelectorMut { Ok(self) } + pub fn compiled_path(&mut self, node: Node) -> &mut Self { + self.path = Some(node); + self + } + pub fn value(&mut self, value: Value) -> &mut Self { self.value = Some(value); self diff --git a/tests/readme.rs b/tests/readme.rs index cb6e38e..f328c79 100644 --- a/tests/readme.rs +++ b/tests/readme.rs @@ -4,8 +4,9 @@ extern crate serde; extern crate serde_json; use serde::Deserialize; +use serde_json::Value; -use jsonpath::Selector; +use jsonpath::{Selector, SelectorMut}; #[test] fn readme() { @@ -164,6 +165,49 @@ fn readme_selector() { assert_eq!(vec![Friend { name: "친구3".to_string(), age: Some(30) }], result); } +#[test] +fn readme_selector_mut() { + let json_obj = json!({ + "school": { + "friends": [ + {"name": "친구1", "age": 20}, + {"name": "친구2", "age": 20} + ] + }, + "friends": [ + {"name": "친구3", "age": 30}, + {"name": "친구4"} + ]}); + + let mut selector_mut = SelectorMut::new(); + + let result = selector_mut + .str_path("$..[?(@.age == 20)].age").unwrap() + .value(json_obj) + .replace_with(&mut |v| { + let age = if let Value::Number(n) = v { + n.as_u64().unwrap() * 2 + } else { + 0 + }; + + json!(age) + }).unwrap() + .take().unwrap(); + + assert_eq!(result, json!({ + "school": { + "friends": [ + {"name": "친구1", "age": 40}, + {"name": "친구2", "age": 40} + ] + }, + "friends": [ + {"name": "친구3", "age": 30}, + {"name": "친구4"} + ]})); +} + #[test] fn readme_select() { let json_obj = json!({ @@ -240,7 +284,7 @@ fn readme_select_as() { #[test] fn readme_compile() { - let mut template = jsonpath::compile("$..friends[0]"); + let mut first_firend = jsonpath::compile("$..friends[0]"); let json_obj = json!({ "school": { @@ -254,7 +298,7 @@ fn readme_compile() { {"name": "친구4"} ]}); - let json = template(&json_obj).unwrap(); + let json = first_firend(&json_obj).unwrap(); assert_eq!(json, vec![ &json!({"name": "친구3", "age": 30}), @@ -331,4 +375,71 @@ fn readme_selector_as() { ); assert_eq!(json, ret); +} + + +#[test] +fn readme_delete() { + let json_obj = json!({ + "school": { + "friends": [ + {"name": "친구1", "age": 20}, + {"name": "친구2", "age": 20} + ] + }, + "friends": [ + {"name": "친구3", "age": 30}, + {"name": "친구4"} + ]}); + + let ret = jsonpath::delete(json_obj, "$..[?(20 == @.age)]").unwrap(); + + assert_eq!(ret, json!({ + "school": { + "friends": [ + null, + null + ] + }, + "friends": [ + {"name": "친구3", "age": 30}, + {"name": "친구4"} + ]})); +} + +#[test] +fn readme_replace_with() { + let json_obj = json!({ + "school": { + "friends": [ + {"name": "친구1", "age": 20}, + {"name": "친구2", "age": 20} + ] + }, + "friends": [ + {"name": "친구3", "age": 30}, + {"name": "친구4"} + ]}); + + let result = jsonpath::replace_with(json_obj, "$..[?(@.age == 20)].age", &mut |v| { + let age = if let Value::Number(n) = v { + n.as_u64().unwrap() * 2 + } else { + 0 + }; + + json!(age) + }).unwrap(); + + assert_eq!(result, json!({ + "school": { + "friends": [ + {"name": "친구1", "age": 40}, + {"name": "친구2", "age": 40} + ] + }, + "friends": [ + {"name": "친구3", "age": 30}, + {"name": "친구4"} + ]})); } \ No newline at end of file