diff --git a/src/lib.rs b/src/lib.rs index e8f03f49..5e5a9280 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -474,6 +474,57 @@ where Ok(value.take().unwrap_or(Value::Null)) } +/// Select JSON properties using a jsonpath, exposing the path to the values with the value itself. Allows updating of the value while tracking field names/paths. +/// +/// ```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_tokens(json_obj, "$..[?(@.age == 20)].age", &mut |v, _tokens| { +/// let age = if let Value::Number(n) = v { +/// n.as_u64().unwrap() * 2 +/// } else { +/// 0 +/// }; +/// +/// Ok(Some(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_tokens(value: Value, path: &str, fun: &mut F) -> Result + where + F: FnMut(Value, &[String]) -> Result, JsonPathError>, +{ + let mut selector = SelectorMut::default(); + let value = selector.str_path(path)?.value(value).replace_with_tokens(fun)?; + Ok(value.take().unwrap_or(Value::Null)) +} + /// A pre-compiled expression. /// /// Calling the select function of this struct will re-use the existing, compiled expression. diff --git a/src/select/mod.rs b/src/select/mod.rs index d87a578e..cfc17f50 100644 --- a/src/select/mod.rs +++ b/src/select/mod.rs @@ -782,6 +782,61 @@ pub struct SelectorMut { value: Option, } +fn replace_value_with_tokens Result, JsonPathError>>( + mut tokens: Vec, + value: &mut Value, + fun: &mut F, +) -> Result<(), JsonPathError> { + let mut target = value; + + let tokens_clone = tokens.clone(); + let last_index = tokens.len().saturating_sub(1); + for (i, token) in tokens.drain(..).enumerate() { + let target_once = target; + let is_last = i == last_index; + let target_opt = match *target_once { + Value::Object(ref mut map) => { + if is_last { + if let Entry::Occupied(mut e) = map.entry(token) { + let v = e.insert(Value::Null); + if let Some(res) = fun(v,&tokens_clone)? { + e.insert(res); + } else { + e.remove(); + } + } + return Ok(()); + } + map.get_mut(&token) + } + Value::Array(ref mut vec) => { + if let Ok(x) = token.parse::() { + if is_last { + let v = std::mem::replace(&mut vec[x], Value::Null); + if let Some(res) = fun(v,&tokens_clone)? { + vec[x] = res; + } else { + vec.remove(x); + } + return Ok(()); + } + vec.get_mut(x) + } else { + None + } + } + _ => None, + }; + + if let Some(t) = target_opt { + target = t; + } else { + break; + } + } + Ok(()) +} + fn replace_value Result, JsonPathError>>( mut tokens: Vec, value: &mut Value, @@ -962,6 +1017,24 @@ impl SelectorMut { Ok(self) } + + pub fn replace_with_tokens Result, JsonPathError>>( + &mut self, + fun: &mut F + ) -> Result<&mut Self, JsonPathError> { + let paths = { + let result = self.select()?; + self.compute_paths(result) + }; + + if let Some(ref mut value) = &mut self.value { + for tokens in paths { + replace_value_with_tokens(tokens, value, fun)?; + } + } + + Ok(self) + } } @@ -1001,4 +1074,4 @@ mod select_inner_tests { panic!(); } } -} \ No newline at end of file +}