Skip to main content

har/
jsonpath.rs

1use serde_json::Value;
2
3enum Step {
4    Key(String),
5    Index(usize),
6    Wildcard,
7}
8
9fn parse(path: &str) -> Option<Vec<Step>> {
10    let mut steps = Vec::new();
11    let trimmed = path.trim().trim_start_matches('$');
12    for raw in trimmed.split('.') {
13        if raw.is_empty() {
14            continue;
15        }
16        let mut rest = raw;
17        // A segment may be "name", "name[0]", "name[*]", or "[0]".
18        while let Some(lb) = rest.find('[') {
19            let key = &rest[..lb];
20            if !key.is_empty() {
21                steps.push(Step::Key(key.to_string()));
22            }
23            let rb_rel = rest[lb..].find(']')?;
24            let rb = lb + rb_rel;
25            let inner = &rest[lb + 1..rb];
26            if inner == "*" {
27                steps.push(Step::Wildcard);
28            } else {
29                steps.push(Step::Index(inner.parse().ok()?));
30            }
31            rest = &rest[rb + 1..];
32        }
33        if !rest.is_empty() {
34            steps.push(Step::Key(rest.to_string()));
35        }
36    }
37    Some(steps)
38}
39
40/// Evaluate a minimal JSON path (`$.a.b`, `a[0].c`, `errors[*].code`) over a
41/// JSON value, returning all matched values. Returns empty on a bad path.
42pub fn eval(value: &Value, path: &str) -> Vec<Value> {
43    let Some(steps) = parse(path) else {
44        return Vec::new();
45    };
46    let mut current: Vec<&Value> = vec![value];
47    for step in &steps {
48        let mut next: Vec<&Value> = Vec::new();
49        for v in &current {
50            match step {
51                Step::Key(k) => {
52                    if let Some(child) = v.get(k) {
53                        next.push(child);
54                    }
55                }
56                Step::Index(i) => {
57                    if let Some(child) = v.get(i) {
58                        next.push(child);
59                    }
60                }
61                Step::Wildcard => match v {
62                    Value::Array(arr) => next.extend(arr.iter()),
63                    Value::Object(map) => next.extend(map.values()),
64                    _ => {}
65                },
66            }
67        }
68        current = next;
69    }
70    current.into_iter().cloned().collect()
71}
72
73#[cfg(test)]
74mod tests {
75    use super::eval;
76    use serde_json::json;
77
78    #[test]
79    fn nested_key() {
80        let v = json!({"a": {"b": 1}});
81        assert_eq!(eval(&v, "$.a.b"), vec![json!(1)]);
82    }
83
84    #[test]
85    fn array_index() {
86        let v = json!({"errors": [{"code": "E1"}, {"code": "E2"}]});
87        assert_eq!(eval(&v, "errors[0].code"), vec![json!("E1")]);
88    }
89
90    #[test]
91    fn array_wildcard() {
92        let v = json!({"errors": [{"code": "E1"}, {"code": "E2"}]});
93        assert_eq!(eval(&v, "$.errors[*].code"), vec![json!("E1"), json!("E2")]);
94    }
95
96    #[test]
97    fn missing_path_is_empty() {
98        let v = json!({"a": 1});
99        assert!(eval(&v, "$.nope.x").is_empty());
100    }
101
102    #[test]
103    fn invalid_index_is_empty() {
104        let v = json!({"a": [1]});
105        assert!(eval(&v, "a[x]").is_empty());
106    }
107}