blob: e3a034a6443740823150b0dac9c989d811c3cad6 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! Functions for interacting with nested json objects in a recursive way.
use {
anyhow::{Context, Result},
serde_json::{map::Entry, Map, Value},
std::iter::FromIterator,
};
/// A trait that adds a recursive mapping function to a nested json value tree.
///
/// Note that implementations of this should be on value types and not references,
/// with perhaps an additional impl for a reference that clones. This is because most
/// of the time most values won't change, so it makes sense to copy the whole thing up
/// front and only have to build a new item if necessary.
///
/// This also makes it so you can efficiently chain [`RecursiveMap::recursive_map`]
/// calls together, since they will continue to use the same Value items until one
/// is overwritten.
pub(crate) trait RecursiveMap {
/// The type of output that the filtering will return. That type must implement
/// this trait as well.
type Output: RecursiveMap;
/// Filters values recursively through the function provided.
fn recursive_map<T: Fn(Value) -> Option<Value>>(self, mapper: &T) -> Self::Output;
}
impl RecursiveMap for Value {
type Output = Option<Value>;
fn recursive_map<T: Fn(Value) -> Option<Value>>(self, mapper: &T) -> Option<Value> {
match self {
Value::Object(map) => {
let mut result = Map::new();
for (key, value) in map.into_iter() {
let new_value = if value.is_object() {
value.clone().recursive_map(mapper)
} else {
mapper(value.clone())
};
if let Some(new_value) = new_value.clone() {
result.insert(key.clone(), new_value);
}
}
if result.len() == 0 {
None
} else {
mapper(Value::Object(result))
}
}
Value::Array(arr) => {
let result =
Vec::from_iter(arr.into_iter().filter_map(|value| value.recursive_map(mapper)));
if result.len() == 0 {
None
} else {
mapper(Value::Array(result))
}
}
other => mapper(other),
}
}
}
impl RecursiveMap for &Value {
type Output = Option<Value>;
fn recursive_map<T: Fn(Value) -> Option<Value>>(self, mapper: &T) -> Self::Output {
self.clone().recursive_map(mapper)
}
}
impl RecursiveMap for Option<&Value> {
type Output = Option<Value>;
fn recursive_map<T: Fn(Value) -> Option<Value>>(self, mapper: &T) -> Self::Output {
self.and_then(|value| value.clone().recursive_map(mapper))
}
}
impl RecursiveMap for Option<Value> {
type Output = Option<Value>;
fn recursive_map<T: Fn(Value) -> Option<Value>>(self, mapper: &T) -> Self::Output {
self.and_then(|value| value.recursive_map(mapper))
}
}
/// Search the given nested json value for the given `key`, and then recurse through
/// the rest of the tree for the `remaining_keys`. Returns the value at that position if found.
pub(crate) fn nested_get<'a>(
cur: Option<&'a Value>,
key: &str,
remaining_keys: &[&str],
) -> Option<&'a Value> {
cur.and_then(|cur| {
if remaining_keys.len() == 0 {
cur.get(key)
} else {
nested_get(cur.get(key), remaining_keys[0], &remaining_keys[1..])
}
})
}
/// Find `key` in `cur`, then recurisively search for the `remaining_keys` through the nested
/// object for the position to insert, creating Object entries as it goes if necessary (including
/// overwriting leaf values in the way). Sets the value if not already set to the same value,
/// and returns true if it did (or false if it already existed as the same value).
pub(crate) fn nested_set(
cur: &mut Map<String, Value>,
key: &str,
remaining_keys: &[&str],
value: Value,
) -> bool {
if remaining_keys.len() == 0 {
// Exit early if the value hasn't changed.
if let Some(old_value) = cur.get(key) {
if old_value == &value {
return false;
}
}
cur.insert(key.to_string(), value);
true
} else {
if let Entry::Occupied(mut occupied) = cur.entry(key) {
let val = occupied.get_mut();
if let Value::Object(ref mut next_map) = val {
nested_set(next_map, remaining_keys[0], &remaining_keys[1..], value)
} else {
let mut next_map = Map::new();
nested_set(&mut next_map, remaining_keys[0], &remaining_keys[1..], value);
*val = Value::Object(next_map);
// since we're creating the rest of the tree, we know we either succeeded and inserted or failed and crashed.
true
}
} else {
let mut next_map = Map::new();
nested_set(&mut next_map, remaining_keys[0], &remaining_keys[1..], value);
cur.insert(key.to_string(), Value::Object(next_map));
// since we're creating the rest of the tree, we know we either succeeded and inserted or failed and crashed.
true
}
}
}
/// Searches the nested object to find the given `key`, then recursively through the `remaining_keys`,
/// and removes it if found. If the key or any of its parent keys don't exist, it will return an error.
pub(crate) fn nested_remove(
cur: &mut Map<String, Value>,
key: &str,
remaining_keys: &[&str],
) -> Result<()> {
if remaining_keys.len() == 0 {
cur.remove(&key.to_string()).context("Config key not found").map(|_| ())
} else {
// Just ensured this would be the case.
let next_map = cur
.get_mut(key)
.context("Configuration key not found.")?
.as_object_mut()
.context("Configuration literal found when expecting a map.")?;
nested_remove(next_map, remaining_keys[0], &remaining_keys[1..])?;
if next_map.len() == 0 {
cur.remove(key).context("Current key not found trying to recursively remove")?;
}
Ok(())
}
}