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(&self, path: &str) -> Option where T: DeserializeOwned; /// Get an item, or a default value. /// /// # Special keys /// see `dot_get()` /// /// # Panics /// see `dot_get()` fn dot_get_or(&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(&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` /// - `(&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(&mut self, path: &str, value: T) -> Option 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(&mut self, path: &str) -> Option 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::(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(&self, path: &str) -> Option 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(&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(&mut self, path: &str, value: T) -> Option 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(&mut self, path: &str) -> Option 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(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::().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 { fn dot_get(&self, path: &str) -> Option 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> .unwrap_or_default() } else { self.get(my) .map(ToOwned::to_owned) .map(serde_json::from_value) .transpose() // Option to Result