use serde_json::Value;
use std::fmt;
#[derive(Clone, Copy)]
pub(crate) enum JsonPath<'a> {
Root(&'a str),
Key(&'a JsonPath<'a>, &'a str),
Index(&'a JsonPath<'a>, usize),
}
impl fmt::Display for JsonPath<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
JsonPath::Root(prefix) => f.write_str(prefix),
JsonPath::Key(parent, k) => {
parent.fmt(f)?;
f.write_str("/")?;
f.write_str(k)
}
JsonPath::Index(parent, i) => {
parent.fmt(f)?;
write!(f, "/{i}")
}
}
}
}
pub(crate) fn small_get<'a>(
map: &'a serde_json::Map<String, Value>,
key: &str,
) -> Option<&'a Value> {
if map.len() > 8 {
return map.get(key);
}
map.iter().find_map(|(k, v)| (k == key).then_some(v))
}
pub(crate) fn visit_values(root: &Value, f: &mut impl FnMut(&JsonPath<'_>, &Value)) {
fn go(v: &Value, at: &JsonPath<'_>, f: &mut impl FnMut(&JsonPath<'_>, &Value)) {
f(at, v);
match v {
Value::Array(arr) => {
for (i, child) in arr.iter().enumerate() {
go(child, &JsonPath::Index(at, i), f);
}
}
Value::Object(obj) => {
for (k, child) in obj {
go(child, &JsonPath::Key(at, k), f);
}
}
_ => {}
}
}
go(root, &JsonPath::Root(""), f);
}
pub(crate) fn find_value_path(
root: &Value,
hit: &mut impl FnMut(&Value) -> bool,
) -> Option<String> {
fn go(v: &Value, at: &JsonPath<'_>, hit: &mut impl FnMut(&Value) -> bool) -> Option<String> {
if hit(v) {
return Some(at.to_string());
}
match v {
Value::Array(arr) => {
for (i, child) in arr.iter().enumerate() {
if let Some(p) = go(child, &JsonPath::Index(at, i), hit) {
return Some(p);
}
}
}
Value::Object(obj) => {
for (k, child) in obj {
if let Some(p) = go(child, &JsonPath::Key(at, k), hit) {
return Some(p);
}
}
}
_ => {}
}
None
}
go(root, &JsonPath::Root(""), hit)
}
pub(crate) fn visit_values_mut(root: &mut Value, f: &mut impl FnMut(&mut Value)) {
let done: Result<(), std::convert::Infallible> = try_visit_values_mut(root, &mut |v| {
f(v);
Ok(())
});
match done {
Ok(()) => {}
Err(never) => match never {},
}
}
pub(crate) fn try_visit_values_mut<E>(
root: &mut Value,
f: &mut impl FnMut(&mut Value) -> Result<(), E>,
) -> Result<(), E> {
f(root)?;
match root {
Value::Array(arr) => {
for child in arr {
try_visit_values_mut(child, f)?;
}
}
Value::Object(obj) => {
for (_, child) in obj.iter_mut() {
try_visit_values_mut(child, f)?;
}
}
_ => {}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn visit_values_order_and_paths() {
let doc = json!({"b": [1, {"a": "x"}], "a": true});
let mut seen = Vec::new();
visit_values(&doc, &mut |path, v| {
seen.push((path.to_string(), v.clone()));
});
assert_eq!(
seen,
vec![
("".to_string(), doc.clone()),
("/b".to_string(), json!([1, {"a": "x"}])),
("/b/0".to_string(), json!(1)),
("/b/1".to_string(), json!({"a": "x"})),
("/b/1/a".to_string(), json!("x")),
("/a".to_string(), json!(true)),
]
);
}
#[test]
fn find_value_path_returns_the_first_hit_in_pre_order() {
let doc = json!({"b": [1, {"op": "x"}], "a": {"op": "x"}});
let mut is_x = |v: &Value| v.get("op").and_then(|o| o.as_str()) == Some("x");
assert_eq!(find_value_path(&doc, &mut is_x), Some("/b/1".to_string()));
assert_eq!(
find_value_path(&json!({"op": "x"}), &mut is_x),
Some(String::new())
);
assert_eq!(find_value_path(&json!([1, 2]), &mut is_x), None);
}
#[test]
fn visit_values_mut_descends_into_replacements() {
let mut doc = json!({"x": {"expand": true}});
let mut visits = 0usize;
visit_values_mut(&mut doc, &mut |v| {
visits += 1;
if v.get("expand").is_some() {
*v = json!({"child": 7});
}
});
assert_eq!(doc, json!({"x": {"child": 7}}));
assert_eq!(visits, 3);
}
#[test]
fn try_visit_values_mut_short_circuits() {
let mut doc = json!([1, 2, 3, 4]);
let mut touched = Vec::new();
let out: Result<(), String> = try_visit_values_mut(&mut doc, &mut |v| {
if let Some(n) = v.as_i64() {
touched.push(n);
if n == 2 {
return Err("stop".to_string());
}
}
Ok(())
});
assert_eq!(out, Err("stop".to_string()));
assert_eq!(touched, vec![1, 2]);
}
}