commit
						7454ccdfe2
					
				| @ -0,0 +1,4 @@ | ||||
| /target | ||||
| **/*.rs.bk | ||||
| Cargo.lock | ||||
| .idea/ | ||||
| @ -0,0 +1,12 @@ | ||||
| [package] | ||||
| name = "json_dotpath" | ||||
| version = "0.1.0" | ||||
| authors = ["Ondřej Hruška <ondra@ondrovo.com>"] | ||||
| edition = "2018" | ||||
| 
 | ||||
| # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||||
| 
 | ||||
| [dependencies] | ||||
| serde = "1" | ||||
| serde_derive = "1" | ||||
| serde_json = "1" | ||||
| @ -0,0 +1,776 @@ | ||||
| use serde::de::DeserializeOwned; | ||||
| use serde::Serialize; | ||||
| use serde_json::{Map, Value}; | ||||
| use std::cmp::Ordering; | ||||
| use std::mem; | ||||
| 
 | ||||
| /// Access and mutate nested JSON elements by dotted paths
 | ||||
| ///
 | ||||
| /// The path is composed of keys separated by dots, e.g. `foo.bar.1`.
 | ||||
| ///
 | ||||
| /// Arrays are indexed by numeric strings or special keys (see `dot_get()` and `dot_set()`).
 | ||||
| ///
 | ||||
| /// This trait is implemented for `serde_json::Value`, specifically the
 | ||||
| /// `Map`, `Array`, and `Null` variants. Empty path can also be used to access a scalar.
 | ||||
| pub trait DotPaths { | ||||
|     /// Get an item by path, if present.
 | ||||
|     ///
 | ||||
|     /// JSON `null` becomes `None`, same as unpopulated path.
 | ||||
|     ///
 | ||||
|     /// # Special keys
 | ||||
|     /// Arrays can be indexed by special keys for reading:
 | ||||
|     /// - `>` ... last element
 | ||||
|     ///
 | ||||
|     /// # Panics
 | ||||
|     /// - If the path attempts to index into a scalar (e.g. `"foo.bar"` in `{"foo": 123}`)
 | ||||
|     /// - If the path uses invalid key in an array or map
 | ||||
|     fn dot_get<T>(&self, path: &str) -> Option<T> | ||||
|     where | ||||
|         T: DeserializeOwned; | ||||
| 
 | ||||
|     /// Get an item, or a default value.
 | ||||
|     ///
 | ||||
|     /// # Special keys
 | ||||
|     /// see `dot_get()`
 | ||||
|     ///
 | ||||
|     /// # Panics
 | ||||
|     /// see `dot_get()`
 | ||||
|     fn dot_get_or<T>(&self, path: &str, def: T) -> T | ||||
|     where | ||||
|         T: DeserializeOwned, | ||||
|     { | ||||
|         self.dot_get(path).unwrap_or(def) | ||||
|     } | ||||
| 
 | ||||
|     /// Get an item, or a default value using the Default trait
 | ||||
|     ///
 | ||||
|     /// # Special keys
 | ||||
|     /// see `dot_get()`
 | ||||
|     ///
 | ||||
|     /// # Panics
 | ||||
|     /// see `dot_get()`
 | ||||
|     fn dot_get_or_default<T>(&self, path: &str) -> T | ||||
|     where | ||||
|         T: DeserializeOwned + Default, | ||||
|     { | ||||
|         self.dot_get(path).unwrap_or_default() | ||||
|     } | ||||
| 
 | ||||
|     /// Get a mutable reference to an item
 | ||||
|     ///
 | ||||
|     /// # Special keys
 | ||||
|     /// see `dot_get()`
 | ||||
|     ///
 | ||||
|     /// # Panics
 | ||||
|     /// see `dot_get()`
 | ||||
|     fn dot_get_mut(&mut self, path: &str) -> Option<&mut Value>; | ||||
| 
 | ||||
|     /// Insert an item by path.
 | ||||
|     ///
 | ||||
|     /// # Special keys
 | ||||
|     /// Arrays can be indexed by special keys:
 | ||||
|     /// - `+` or `>` ... append
 | ||||
|     /// - `-` or `<` ... prepend
 | ||||
|     /// - `>n` ... insert after an index `n`
 | ||||
|     /// - `<n` ... insert before an index `n`
 | ||||
|     ///
 | ||||
|     /// # Panics
 | ||||
|     /// see `dot_get()`
 | ||||
|     fn dot_set<T>(&mut self, path: &str, value: T) | ||||
|     where | ||||
|         T: Serialize; | ||||
| 
 | ||||
|     /// Replace a value by path with a new value.
 | ||||
|     /// The value types do not have to match.
 | ||||
|     ///
 | ||||
|     /// # Panics
 | ||||
|     /// see `dot_get()`
 | ||||
|     fn dot_replace<T, U>(&mut self, path: &str, value: T) -> Option<U> | ||||
|     where | ||||
|         T: Serialize, | ||||
|         U: DeserializeOwned; | ||||
| 
 | ||||
|     /// Get an item using a path, removing it from the store.
 | ||||
|     /// If no item was stored under this path, then None is returned.
 | ||||
|     ///
 | ||||
|     /// # Panics
 | ||||
|     /// see `dot_get()`
 | ||||
|     fn dot_take<T>(&mut self, path: &str) -> Option<T> | ||||
|     where | ||||
|         T: DeserializeOwned; | ||||
| 
 | ||||
|     /// Remove an item matching a key.
 | ||||
|     /// Returns true if any item was removed.
 | ||||
|     ///
 | ||||
|     /// # Panics
 | ||||
|     /// see `dot_get()`
 | ||||
|     fn dot_remove(&mut self, path: &str) -> bool { | ||||
|         self.dot_take::<Value>(path).is_some() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// Split the path string by dot, if present.
 | ||||
| ///
 | ||||
| /// Returns a tuple of (before_dot, after_dot)
 | ||||
| fn path_split(path: &str) -> (&str, Option<&str>) { | ||||
|     let dot = path.find('.'); | ||||
|     match dot { | ||||
|         None => (path, None), | ||||
|         Some(pos) => (&path[0..pos], Some(&path[pos + 1..])), | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl DotPaths for serde_json::Value { | ||||
|     fn dot_get<T>(&self, path: &str) -> Option<T> | ||||
|     where | ||||
|         T: DeserializeOwned, | ||||
|     { | ||||
|         match self { | ||||
|             Value::Array(vec) => vec.dot_get(path), | ||||
|             Value::Object(map) => map.dot_get(path), | ||||
|             _ => { | ||||
|                 if path.is_empty() { | ||||
|                     serde_json::from_value(self.to_owned()).ok() | ||||
|                 } else { | ||||
|                     panic!("Node is not array or object!"); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fn dot_get_mut(&mut self, path: &str) -> Option<&mut Value> { | ||||
|         match self { | ||||
|             Value::Array(vec) => vec.dot_get_mut(path), | ||||
|             Value::Object(map) => map.dot_get_mut(path), | ||||
|             _ => { | ||||
|                 if path.is_empty() { | ||||
|                     Some(self) | ||||
|                 } else { | ||||
|                     panic!("Node is not array or object!"); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fn dot_set<T>(&mut self, path: &str, value: T) | ||||
|     where | ||||
|         T: Serialize, | ||||
|     { | ||||
|         match self { | ||||
|             Value::Array(vec) => { | ||||
|                 vec.dot_set(path, value); | ||||
|             } | ||||
|             Value::Object(map) => { | ||||
|                 map.dot_set(path, value); | ||||
|             } | ||||
|             Value::Null => { | ||||
|                 mem::replace(self, new_by_path_root(path, value)); | ||||
|             } | ||||
|             _ => { | ||||
|                 if path.is_empty() { | ||||
|                     mem::replace(self, serde_json::to_value(value).expect("Serialize error")); | ||||
|                 } else { | ||||
|                     panic!("Node is not an array, object, or null!"); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fn dot_replace<T, U>(&mut self, path: &str, value: T) -> Option<U> | ||||
|     where | ||||
|         T: Serialize, | ||||
|         U: DeserializeOwned, | ||||
|     { | ||||
|         match self { | ||||
|             Value::Array(vec) => vec.dot_replace(path, value), | ||||
|             Value::Object(map) => map.dot_replace(path, value), | ||||
|             Value::Null => { | ||||
|                 self.dot_set(path, value); | ||||
|                 None | ||||
|             } | ||||
|             _ => { | ||||
|                 if path.is_empty() { | ||||
|                     let new = serde_json::to_value(value).expect("Serialize error"); | ||||
|                     let old = mem::replace(self, new); | ||||
|                     Some(serde_json::from_value(old).expect("Unserialize error")) | ||||
|                 } else { | ||||
|                     panic!("Node is not an array, object, or null!") | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fn dot_take<T>(&mut self, path: &str) -> Option<T> | ||||
|     where | ||||
|         T: DeserializeOwned, | ||||
|     { | ||||
|         match self { | ||||
|             Value::Array(vec) => vec.dot_take(path), | ||||
|             Value::Object(map) => map.dot_take(path), | ||||
|             Value::Null => None, | ||||
|             _ => { | ||||
|                 if path.is_empty() { | ||||
|                     let old = mem::replace(self, Value::Null); | ||||
|                     Some(serde_json::from_value(old).expect("Unserialize error")) | ||||
|                 } else { | ||||
|                     panic!("Node is not an array, object, or null!") | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// Create a Value::Object or Value::Array based on a nested path.
 | ||||
| ///
 | ||||
| /// Builds the parent path to a non-existent key in set-type operations.
 | ||||
| fn new_by_path_root<T>(path: &str, value: T) -> Value | ||||
| where | ||||
|     T: Serialize, | ||||
| { | ||||
|     let (sub1, _) = path_split(path); | ||||
|     if sub1 == "0" || sub1 == "+" || sub1 == "<" || sub1 == ">" { | ||||
|         // new vec
 | ||||
|         let mut new_vec = vec![]; | ||||
|         new_vec.dot_set(path, value); | ||||
|         Value::Array(new_vec) | ||||
|     } else { | ||||
|         // new map
 | ||||
|         let mut new_map = Map::new(); | ||||
|         new_map.dot_set(path, value); | ||||
|         Value::Object(new_map) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// Check if a key is valid to use by dot paths in Value::Object.
 | ||||
| /// The key must start with an alpha character or underscore and must not contain period.
 | ||||
| fn validate_map_key(key: &str) { | ||||
|     if key.parse::<usize>().is_ok() { | ||||
|         panic!("Numeric keys are not allowed in maps: {}", key); | ||||
|     } | ||||
| 
 | ||||
|     if !key.starts_with(|p: char| p.is_ascii_alphabetic() || p == '_') || key.contains('.') { | ||||
|         panic!("Invalid map key: {}", key); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl DotPaths for serde_json::Map<String, serde_json::Value> { | ||||
|     fn dot_get<T>(&self, path: &str) -> Option<T> | ||||
|     where | ||||
|         T: DeserializeOwned, | ||||
|     { | ||||
|         let (my, sub) = path_split(path); | ||||
|         validate_map_key(my); | ||||
| 
 | ||||
|         if let Some(sub_path) = sub { | ||||
|             self.get(my) | ||||
|                 .map(|child| child.dot_get(sub_path)) // this produces Option<Option<T>>
 | ||||
|                 .unwrap_or_default() | ||||
|         } else { | ||||
|             self.get(my) | ||||
|                 .map(ToOwned::to_owned) | ||||
|                 .map(serde_json::from_value) | ||||
|                 .transpose() // Option<Result> to Result<Option>
 | ||||
|                 .unwrap_or_default() // get rid of the result
 | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fn dot_get_mut(&mut self, path: &str) -> Option<&mut Value> { | ||||
|         let (my, sub) = path_split(path); | ||||
|         validate_map_key(my); | ||||
| 
 | ||||
|         if let Some(sub_path) = sub { | ||||
|             self.get_mut(my) | ||||
|                 .map(|m| m.get_mut(sub_path)) | ||||
|                 .unwrap_or_default() | ||||
|         } else { | ||||
|             self.get_mut(my) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fn dot_set<T>(&mut self, path: &str, value: T) | ||||
|     where | ||||
|         T: Serialize, | ||||
|     { | ||||
|         let (my, sub) = path_split(path); | ||||
|         validate_map_key(my); | ||||
| 
 | ||||
|         if let Some(subpath) = sub { | ||||
|             if self.contains_key(my) { | ||||
|                 self.get_mut(my).unwrap().dot_set(subpath, value); | ||||
|             } else { | ||||
|                 self.insert(my.into(), new_by_path_root(subpath, value)); | ||||
|             } | ||||
|         } else { | ||||
|             let packed = serde_json::to_value(value).expect("Serialize error"); | ||||
|             self.insert(my.into(), packed); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fn dot_replace<T, U>(&mut self, path: &str, value: T) -> Option<U> | ||||
|     where | ||||
|         T: Serialize, | ||||
|         U: DeserializeOwned, | ||||
|     { | ||||
|         let (my, sub) = path_split(path); | ||||
|         validate_map_key(my); | ||||
| 
 | ||||
|         if let Some(subpath) = sub { | ||||
|             if self.contains_key(my) { | ||||
|                 self.get_mut(my).unwrap().dot_replace(subpath, value) | ||||
|             } else { | ||||
|                 self.dot_set(path, value); | ||||
|                 None | ||||
|             } | ||||
|         } else { | ||||
|             let packed = serde_json::to_value(value).expect("Serialize error"); | ||||
|             self.insert(my.to_string(), packed) | ||||
|                 .map(serde_json::from_value) | ||||
|                 .transpose() | ||||
|                 .expect("Unserialize error") | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fn dot_take<T>(&mut self, path: &str) -> Option<T> | ||||
|     where | ||||
|         T: DeserializeOwned, | ||||
|     { | ||||
|         let (my, sub) = path_split(path); | ||||
|         validate_map_key(my); | ||||
| 
 | ||||
|         if let Some(subpath) = sub { | ||||
|             if let Some(item) = self.get_mut(my) { | ||||
|                 item.dot_take(subpath) | ||||
|             } else { | ||||
|                 None | ||||
|             } | ||||
|         } else { | ||||
|             self.remove(my) | ||||
|                 .map(serde_json::from_value) | ||||
|                 .transpose() // Option<Result> to Result<Option>
 | ||||
|                 .expect("Unserialize error") // get rid of the result
 | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl DotPaths for Vec<serde_json::Value> { | ||||
|     fn dot_get<T>(&self, path: &str) -> Option<T> | ||||
|     where | ||||
|         T: DeserializeOwned, | ||||
|     { | ||||
|         let (my, sub) = path_split(path); | ||||
| 
 | ||||
|         let index: usize = if my == ">" { | ||||
|             self.len() - 1 | ||||
|         } else { | ||||
|             my.parse().unwrap() | ||||
|         }; | ||||
| 
 | ||||
|         if let Some(subpath) = sub { | ||||
|             self.get(index) | ||||
|                 .map(ToOwned::to_owned) | ||||
|                 .map(|child| child.dot_get(subpath)) | ||||
|                 .unwrap_or_default() | ||||
|         } else { | ||||
|             self.get(index) | ||||
|                 .map(ToOwned::to_owned) | ||||
|                 .map(serde_json::from_value) | ||||
|                 .transpose() | ||||
|                 .unwrap_or_default() | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fn dot_get_mut(&mut self, path: &str) -> Option<&mut Value> { | ||||
|         let (my, sub) = path_split(path); | ||||
| 
 | ||||
|         let my: usize = my | ||||
|             .parse() | ||||
|             .unwrap_or_else(|_| panic!("Cannot index an array by \"{}\"", my)); | ||||
| 
 | ||||
|         if let Some(subpath) = sub { | ||||
|             self.get_mut(my) | ||||
|                 .map(|m: &mut Value| m.get_mut(subpath)) | ||||
|                 .unwrap_or_default() | ||||
|         } else { | ||||
|             self.get_mut(my) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fn dot_set<T>(&mut self, path: &str, value: T) | ||||
|     where | ||||
|         T: Serialize, | ||||
|     { | ||||
|         let (mut my, sub) = path_split(path); | ||||
| 
 | ||||
|         if my.is_empty() { | ||||
|             panic!("Cannot index array by empty key"); | ||||
|         } | ||||
| 
 | ||||
|         let mut insert = false; | ||||
|         let mut add = 0isize; | ||||
| 
 | ||||
|         if my.starts_with('<') { | ||||
|             // "<n" means insert before n
 | ||||
|             // "<" means prepend
 | ||||
|             insert = true; | ||||
|             my = &my[1..]; | ||||
|         } else if my.starts_with('>') { | ||||
|             insert = true; | ||||
|             my = &my[1..]; | ||||
|             if my.is_empty() { | ||||
|                 // ">" means append
 | ||||
|                 add = self.len() as isize; | ||||
|             } else { | ||||
|                 // ">n" means insert after
 | ||||
|                 add = 1; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         let mut my = match my { | ||||
|             // append
 | ||||
|             "+" => self.len(), | ||||
|             // prepend
 | ||||
|             "-" => { | ||||
|                 insert = true; | ||||
|                 0 | ||||
|             } | ||||
|             // empty (this happens only if the < or > was removed above)
 | ||||
|             "" => 0, | ||||
|             _ => my | ||||
|                 .parse() | ||||
|                 .unwrap_or_else(|_| panic!("Cannot index an array by \"{}\"", my)), | ||||
|         }; | ||||
| 
 | ||||
|         my = (my as isize + add) as usize; | ||||
| 
 | ||||
|         match my.cmp(&self.len()) { | ||||
|             Ordering::Less => { | ||||
|                 if insert { | ||||
|                     // insert
 | ||||
|                     if let Some(subpath) = sub { | ||||
|                         self.insert(my, new_by_path_root(subpath, value)); | ||||
|                     } else { | ||||
|                         self.insert(my, serde_json::to_value(value).expect("Serialize error")); | ||||
|                     } | ||||
|                 } else { | ||||
|                     // replace
 | ||||
|                     if let Some(subpath) = sub { | ||||
|                         self[my].dot_set(subpath, value); | ||||
|                     } else { | ||||
|                         self[my] = serde_json::to_value(value).expect("Serialize error"); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             Ordering::Equal => { | ||||
|                 if let Some(subpath) = sub { | ||||
|                     self.push(new_by_path_root(subpath, value)) | ||||
|                 } else { | ||||
|                     self.push(serde_json::to_value(value).expect("Serialize error")) | ||||
|                 } | ||||
|             } | ||||
|             Ordering::Greater => { | ||||
|                 panic!("Index out of bounds."); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fn dot_replace<T, U>(&mut self, path: &str, value: T) -> Option<U> | ||||
|     where | ||||
|         T: Serialize, | ||||
|         U: DeserializeOwned, | ||||
|     { | ||||
|         let (my, sub) = path_split(path); | ||||
|         let index = my | ||||
|             .parse::<usize>() | ||||
|             .unwrap_or_else(|_| panic!("Cannot index an array by \"{}\"", my)); | ||||
| 
 | ||||
|         if let Some(subpath) = sub { | ||||
|             if index < self.len() { | ||||
|                 let a_mut: &mut Value = self.get_mut(index).unwrap(); | ||||
|                 a_mut.dot_replace(subpath, value) | ||||
|             } else { | ||||
|                 // use the append implementation & validations from dot_set
 | ||||
|                 self.dot_set(path, value); | ||||
|                 None | ||||
|             } | ||||
|         } else { | ||||
|             // No subpath
 | ||||
|             if index < self.len() { | ||||
|                 let other = serde_json::to_value(value).expect("Serialize error"); | ||||
|                 let old = mem::replace(&mut self[index], other); | ||||
|                 Some(serde_json::from_value(old).expect("Deserialize error")) | ||||
|             } else { | ||||
|                 // use the append implementation & validations from dot_set
 | ||||
|                 self.dot_set(my, value); | ||||
|                 None | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fn dot_take<T>(&mut self, path: &str) -> Option<T> | ||||
|     where | ||||
|         T: DeserializeOwned, | ||||
|     { | ||||
|         let (my, sub) = path_split(path); | ||||
|         let my = my | ||||
|             .parse() | ||||
|             .unwrap_or_else(|_| panic!("Cannot index an array by \"{}\"", my)); | ||||
| 
 | ||||
|         if my >= self.len() { | ||||
|             return None; | ||||
|         } | ||||
| 
 | ||||
|         if let Some(subpath) = sub { | ||||
|             let value: &mut Value = &mut self[my]; | ||||
|             value.dot_take(subpath) | ||||
|         } else { | ||||
|             // bounds are checked above
 | ||||
|             serde_json::from_value(self.remove(my)).expect("Deserialize error") | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use crate::DotPaths; | ||||
|     use serde_json::json; | ||||
|     use serde_json::Value; | ||||
| 
 | ||||
|     #[test] | ||||
|     fn get_scalar_with_empty_path() { | ||||
|         let value = Value::String("Hello".to_string()); | ||||
|         assert_eq!(Some("Hello".to_string()), value.dot_get("")); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     #[should_panic] | ||||
|     fn cant_get_scalar_with_path() { | ||||
|         let value = Value::String("Hello".to_string()); | ||||
|         let _ = value.dot_get::<Value>("1.2.3"); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn set_vec() { | ||||
|         let mut vec = Value::Array(vec![]); | ||||
|         vec.dot_set("0", "first"); | ||||
|         vec.dot_set("0", "second"); // this replaces it
 | ||||
|         vec.dot_set("1", "third"); | ||||
|         vec.dot_set("+", "append"); | ||||
|         vec.dot_set(">", "append2"); | ||||
|         vec.dot_set("-", "prepend"); | ||||
|         vec.dot_set("<", "prepend2"); | ||||
|         vec.dot_set("<0", "prepend3"); | ||||
|         vec.dot_set(">1", "insert after 1"); | ||||
|         vec.dot_set(">0", "after0"); | ||||
|         vec.dot_set("<2", "before2"); | ||||
|         assert_eq!( | ||||
|             json!([ | ||||
|                 "prepend3", | ||||
|                 "after0", | ||||
|                 "before2", | ||||
|                 "prepend2", | ||||
|                 "insert after 1", | ||||
|                 "prepend", | ||||
|                 "second", | ||||
|                 "third", | ||||
|                 "append", | ||||
|                 "append2" | ||||
|             ]), | ||||
|             vec | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     #[should_panic] | ||||
|     fn set_vec_panic_bad_index() { | ||||
|         let mut vec = Value::Array(vec![]); | ||||
|         vec.dot_set("1", "first"); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     #[should_panic] | ||||
|     fn set_vec_panic_index_not_numeric() { | ||||
|         let mut vec = Value::Array(vec![]); | ||||
|         vec.dot_set("abc", "first"); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn set_vec_spawn() { | ||||
|         let mut vec = Value::Array(vec![]); | ||||
| 
 | ||||
|         vec.dot_set("0.0.0", "first"); | ||||
|         vec.dot_set("+", "append"); | ||||
|         vec.dot_set("<1", "in between"); | ||||
|         assert_eq!(json!([[["first"]], "in between", "append"]), vec); | ||||
| 
 | ||||
|         vec.dot_set("0.0.+", "second"); | ||||
|         assert_eq!(json!([[["first", "second"]], "in between", "append"]), vec); | ||||
| 
 | ||||
|         vec.dot_set("0.+", "mmm"); | ||||
|         assert_eq!( | ||||
|             json!([[["first", "second"], "mmm"], "in between", "append"]), | ||||
|             vec | ||||
|         ); | ||||
| 
 | ||||
|         vec.dot_set("0.+.0", "xyz"); | ||||
|         assert_eq!( | ||||
|             json!([ | ||||
|                 [["first", "second"], "mmm", ["xyz"]], | ||||
|                 "in between", | ||||
|                 "append" | ||||
|             ]), | ||||
|             vec | ||||
|         ); | ||||
| 
 | ||||
|         // append and prepend can also spawn a new array
 | ||||
|         let mut vec = Value::Array(vec![]); | ||||
|         vec.dot_set(">.>.>", "first"); | ||||
|         assert_eq!(json!([[["first"]]]), vec); | ||||
|         vec.dot_set(">.<.>", "second"); | ||||
|         assert_eq!(json!([[["first"]], [["second"]]]), vec); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn get_vec() { | ||||
|         let vec = json!([ | ||||
|             [["first", "second"], "mmm", ["xyz"]], | ||||
|             "in between", | ||||
|             "append" | ||||
|         ]); | ||||
|         assert_eq!(Some("first".to_string()), vec.dot_get("0.0.0")); | ||||
|         assert_eq!(Some("second".to_string()), vec.dot_get("0.0.1")); | ||||
| 
 | ||||
|         // this one doesn't exist
 | ||||
|         assert_eq!(None, vec.dot_get::<String>("0.0.3")); | ||||
| 
 | ||||
|         // get last
 | ||||
|         assert_eq!(Some(json!(["xyz"])), vec.dot_get("0.>")); | ||||
| 
 | ||||
|         // retrieve a Value
 | ||||
|         assert_eq!( | ||||
|             Some(json!([["first", "second"], "mmm", ["xyz"]])), | ||||
|             vec.dot_get("0") | ||||
|         ); | ||||
|         assert_eq!(Some(json!(["first", "second"])), vec.dot_get("0.0")); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     #[should_panic] | ||||
|     fn get_vec_panic_index_scalar() { | ||||
|         let vec = json!([ | ||||
|             [["first", "second"], "mmm", ["xyz"]], | ||||
|             "in between", | ||||
|             "append" | ||||
|         ]); | ||||
|         let _ = vec.dot_get::<Value>("0.0.1.4"); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn take_from_vec() { | ||||
|         let mut vec = json!(["a", "b", "c"]); | ||||
| 
 | ||||
|         // test Take
 | ||||
|         assert_eq!(Some("b".to_string()), vec.dot_take("1")); | ||||
|         assert_eq!(None, vec.dot_take::<Value>("4")); | ||||
|         assert_eq!(json!(["a", "c"]), vec); | ||||
| 
 | ||||
|         let mut vec = json!([[["a"], "b"], "c"]); | ||||
|         assert_eq!(Some("a".to_string()), vec.dot_take("0.0.0")); | ||||
|         assert_eq!(json!([[[], "b"], "c"]), vec); // empty vec is left in place
 | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn remove_from_vec() { | ||||
|         let mut vec = json!(["a", "b", "c"]); | ||||
| 
 | ||||
|         assert_eq!(true, vec.dot_remove("1")); | ||||
|         assert_eq!(false, vec.dot_remove("4")); | ||||
|         assert_eq!(json!(["a", "c"]), vec); | ||||
| 
 | ||||
|         let mut vec = json!([[["a"], "b"], "c"]); | ||||
|         assert_eq!(true, vec.dot_remove("0.0.0")); | ||||
|         assert_eq!(false, vec.dot_remove("0.0.0")); | ||||
|         assert_eq!(json!([[[], "b"], "c"]), vec); // empty vec is left in place
 | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn replace_in_vec() { | ||||
|         let mut vec = json!(["a", "b", "c"]); | ||||
| 
 | ||||
|         assert_eq!(Some("b".to_string()), vec.dot_replace("1", "BBB")); | ||||
| 
 | ||||
|         let mut vec = json!([[["a"], "b"], "c"]); | ||||
|         assert_eq!(Some("a".to_string()), vec.dot_replace("0.0.0", "AAA")); | ||||
|         assert_eq!(json!([[["AAA"], "b"], "c"]), vec); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn set_map() { | ||||
|         let mut vec = json!({}); | ||||
|         vec.dot_set("foo", "bar"); | ||||
|         assert_eq!(json!({"foo": "bar"}), vec); | ||||
| 
 | ||||
|         let mut vec = json!({}); | ||||
|         vec.dot_set("foo.bar.baz", "binx"); | ||||
|         assert_eq!(json!({"foo": {"bar": {"baz": "binx"}}}), vec); | ||||
| 
 | ||||
|         vec.dot_set("foo.bar.abc.0", "aaa"); | ||||
|         assert_eq!(json!({"foo": {"bar": {"baz": "binx","abc": ["aaa"]}}}), vec); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     #[should_panic] | ||||
|     fn set_map_panic_bad_index() { | ||||
|         let mut vec = json!({}); | ||||
|         vec.dot_set("0", "first"); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn get_map() { | ||||
|         let vec = json!({"one": "two"}); | ||||
|         assert_eq!(Some("two".to_string()), vec.dot_get("one")); | ||||
| 
 | ||||
|         let vec = json!({"one": {"two": "three"}}); | ||||
|         assert_eq!(Some("three".to_string()), vec.dot_get("one.two")); | ||||
| 
 | ||||
|         let vec = json!({"one": "two"}); | ||||
|         assert_eq!(None, vec.dot_get::<Value>("xxx")); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn remove_from_map() { | ||||
|         let mut vec = json!({"one": "two", "x": "y"}); | ||||
|         assert_eq!(true, vec.dot_remove("one")); | ||||
|         assert_eq!(json!({"x": "y"}), vec); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn take_from_map() { | ||||
|         let mut vec = json!({"one": "two", "x": "y"}); | ||||
|         assert_eq!(Some("two".to_string()), vec.dot_take("one")); | ||||
|         assert_eq!(json!({"x": "y"}), vec); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn replace_in_map() { | ||||
|         let mut vec = json!({"one": "two", "x": "y"}); | ||||
|         assert_eq!(Some("two".to_string()), vec.dot_replace("one", "fff")); | ||||
|         assert_eq!(json!({"one": "fff", "x": "y"}), vec); | ||||
| 
 | ||||
|         // replace value for string
 | ||||
|         let mut vec = json!({"one": "two", "x": {"bbb": "y"}}); | ||||
|         assert_eq!(Some("y".to_string()), vec.dot_replace("x.bbb", "mm")); | ||||
|         assert_eq!( | ||||
|             Some(json!({"bbb": "mm"})), | ||||
|             vec.dot_replace("x", "betelgeuze") | ||||
|         ); | ||||
|         assert_eq!(json!({"one": "two", "x": "betelgeuze"}), vec); | ||||
| 
 | ||||
|         // nested replace
 | ||||
|         let mut vec = json!({"one": "two", "x": {"bbb": ["y"]}}); | ||||
|         assert_eq!( | ||||
|             Some("y".to_string()), | ||||
|             vec.dot_replace("x.bbb.0", "betelgeuze") | ||||
|         ); | ||||
|         assert_eq!(json!({"one": "two", "x": {"bbb": ["betelgeuze"]}}), vec); | ||||
|     } | ||||
| } | ||||
					Loading…
					
					
				
		Reference in new issue