use serde_json::Value;
#[derive(Debug, Clone)]
pub enum PathSegment {
Key(&'static str),
Index(usize),
}
impl From<&'static str> for PathSegment {
fn from(s: &'static str) -> Self {
PathSegment::Key(s)
}
}
impl From<usize> for PathSegment {
fn from(i: usize) -> Self {
PathSegment::Index(i)
}
}
pub fn nav<'a>(root: &'a Value, path: &[PathSegment]) -> Option<&'a Value> {
let mut current = root;
for segment in path {
current = match segment {
PathSegment::Key(key) => current.get(key)?,
PathSegment::Index(idx) => current.get(idx)?,
};
}
Some(current)
}
pub fn nav_str<'a>(root: &'a Value, path: &[PathSegment]) -> Option<&'a str> {
nav(root, path).and_then(|v| v.as_str())
}
#[allow(dead_code)]
pub fn nav_i64(root: &Value, path: &[PathSegment]) -> Option<i64> {
nav(root, path).and_then(|v| v.as_i64())
}
#[allow(dead_code)]
pub fn nav_u64(root: &Value, path: &[PathSegment]) -> Option<u64> {
nav(root, path).and_then(|v| v.as_u64())
}
pub fn nav_array<'a>(root: &'a Value, path: &[PathSegment]) -> Option<&'a Vec<Value>> {
nav(root, path).and_then(|v| v.as_array())
}
#[allow(dead_code)]
pub fn nav_bool(root: &Value, path: &[PathSegment]) -> Option<bool> {
nav(root, path).and_then(|v| v.as_bool())
}
#[macro_export]
macro_rules! path {
($($segment:expr),* $(,)?) => {
vec![$($crate::nav::PathSegment::from($segment)),*]
};
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_nav_simple() {
let data = json!({"foo": "bar"});
assert_eq!(nav_str(&data, &path!["foo"]), Some("bar"));
}
#[test]
fn test_nav_nested() {
let data = json!({"a": {"b": {"c": 123}}});
assert_eq!(nav_i64(&data, &path!["a", "b", "c"]), Some(123));
}
#[test]
fn test_nav_array() {
let data = json!({"items": [{"name": "first"}, {"name": "second"}]});
assert_eq!(nav_str(&data, &path!["items", 1, "name"]), Some("second"));
}
#[test]
fn test_nav_missing() {
let data = json!({"foo": "bar"});
assert_eq!(nav(&data, &path!["missing"]), None);
}
}