1use serde_json::{Number, Value};
19
20#[derive(Debug, Clone, PartialEq)]
21enum Segment {
22 Names(Vec<String>),
24 Indexes(Vec<i64>),
26 Wildcard,
27 Slice(Option<i64>, Option<i64>),
28 DeepScan(Box<Segment>),
30 Filter(FilterExpr),
31}
32
33#[derive(Debug, Clone, PartialEq)]
34enum FilterExpr {
35 Or(Box<FilterExpr>, Box<FilterExpr>),
36 And(Box<FilterExpr>, Box<FilterExpr>),
37 Not(Box<FilterExpr>),
38 Exists(Operand),
39 Compare(Operand, CmpOp, Operand),
40}
41
42#[derive(Debug, Clone, Copy, PartialEq)]
43enum CmpOp {
44 Eq,
45 Ne,
46 Lt,
47 Le,
48 Gt,
49 Ge,
50}
51
52#[derive(Debug, Clone, PartialEq)]
53enum Operand {
54 Current(Vec<Segment>),
56 Root(Vec<Segment>),
58 Literal(Value),
59}
60
61#[derive(Debug, Clone, PartialEq)]
63pub struct JsonPath {
64 segments: Vec<Segment>,
65}
66
67#[derive(Debug, Clone, PartialEq)]
69pub enum Step {
70 Name(String),
71 Index(i64),
72}
73
74#[derive(Debug, Clone, PartialEq)]
76pub enum PathError {
77 Invalid(String),
79 NoResults(String),
82}
83
84impl JsonPath {
85 pub fn parse(path: &str) -> Result<Self, PathError> {
87 let chars: Vec<char> = path.trim().chars().collect();
88 if chars.first() != Some(&'$') {
89 return Err(PathError::Invalid(format!(
90 "Path must start with '$': {path}"
91 )));
92 }
93 let mut p = Parser { chars, pos: 1 };
94 let segments = p.segments(false)?;
95 if p.pos != p.chars.len() {
96 return Err(PathError::Invalid(format!(
97 "Unexpected character at position {} in path {path}",
98 p.pos
99 )));
100 }
101 Ok(Self { segments })
102 }
103
104 pub fn is_definite(&self) -> bool {
106 self.segments.iter().all(|s| match s {
107 Segment::Names(n) => n.len() == 1,
108 Segment::Indexes(i) => i.len() == 1,
109 _ => false,
110 })
111 }
112
113 pub fn reference_steps(&self) -> Option<Vec<Step>> {
116 if !self.is_definite() {
117 return None;
118 }
119 Some(
120 self.segments
121 .iter()
122 .map(|s| match s {
123 Segment::Names(n) => Step::Name(n[0].clone()),
124 Segment::Indexes(i) => Step::Index(i[0]),
125 _ => unreachable!("definite paths only hold names and indexes"),
126 })
127 .collect(),
128 )
129 }
130
131 pub fn normalized(&self) -> String {
133 let mut out = String::from("$");
134 for s in &self.segments {
135 render_segment(s, &mut out);
136 }
137 out
138 }
139
140 pub fn evaluate(&self, root: &Value) -> Result<Value, PathError> {
143 let results = select(&self.segments, root, root);
144 if self.is_definite() {
145 results
146 .into_iter()
147 .next()
148 .cloned()
149 .ok_or_else(|| PathError::NoResults(self.normalized()))
150 } else {
151 Ok(Value::Array(results.into_iter().cloned().collect()))
152 }
153 }
154}
155
156pub fn evaluate(root: &Value, path: &str) -> Result<Value, PathError> {
158 JsonPath::parse(path)?.evaluate(root)
159}
160
161fn render_segment(s: &Segment, out: &mut String) {
162 match s {
163 Segment::Names(names) => {
164 let inner: Vec<String> = names.iter().map(|n| format!("'{n}'")).collect();
165 out.push('[');
166 out.push_str(&inner.join(","));
167 out.push(']');
168 }
169 Segment::Indexes(idx) => {
170 let inner: Vec<String> = idx.iter().map(|i| i.to_string()).collect();
171 out.push('[');
172 out.push_str(&inner.join(","));
173 out.push(']');
174 }
175 Segment::Wildcard => out.push_str("[*]"),
176 Segment::Slice(a, b) => {
177 out.push('[');
178 if let Some(a) = a {
179 out.push_str(&a.to_string());
180 }
181 out.push(':');
182 if let Some(b) = b {
183 out.push_str(&b.to_string());
184 }
185 out.push(']');
186 }
187 Segment::DeepScan(inner) => {
188 out.push_str("..");
189 render_segment(inner, out);
190 }
191 Segment::Filter(_) => out.push_str("[?]"),
192 }
193}
194
195fn select<'a>(segments: &[Segment], node: &'a Value, root: &'a Value) -> Vec<&'a Value> {
196 let mut current = vec![node];
197 for seg in segments {
198 let mut next = Vec::new();
199 for v in current {
200 apply_segment(seg, v, root, &mut next);
201 }
202 current = next;
203 }
204 current
205}
206
207fn apply_segment<'a>(seg: &Segment, v: &'a Value, root: &'a Value, out: &mut Vec<&'a Value>) {
208 match seg {
209 Segment::Names(names) => {
210 if let Value::Object(o) = v {
211 for n in names {
212 if let Some(child) = o.get(n) {
213 out.push(child);
214 }
215 }
216 }
217 }
218 Segment::Indexes(idx) => {
219 if let Value::Array(a) = v {
220 for &i in idx {
221 if let Some(child) = resolve_index(a, i) {
222 out.push(child);
223 }
224 }
225 }
226 }
227 Segment::Wildcard => match v {
228 Value::Array(a) => out.extend(a.iter()),
229 Value::Object(o) => out.extend(o.values()),
230 _ => {}
231 },
232 Segment::Slice(start, end) => {
233 if let Value::Array(a) = v {
234 let len = a.len() as i64;
235 let norm = |x: i64| if x < 0 { (len + x).max(0) } else { x.min(len) };
236 let s = start.map(norm).unwrap_or(0);
237 let e = end.map(norm).unwrap_or(len);
238 if s < e {
239 out.extend(a[s as usize..e as usize].iter());
240 }
241 }
242 }
243 Segment::DeepScan(inner) => deep_scan(inner, v, root, out),
244 Segment::Filter(expr) => {
245 let items: Vec<&Value> = match v {
246 Value::Array(a) => a.iter().collect(),
247 Value::Object(_) => vec![v],
248 _ => Vec::new(),
249 };
250 for item in items {
251 if eval_filter(expr, item, root) {
252 out.push(item);
253 }
254 }
255 }
256 }
257}
258
259fn deep_scan<'a>(inner: &Segment, v: &'a Value, root: &'a Value, out: &mut Vec<&'a Value>) {
260 apply_segment(inner, v, root, out);
261 match v {
262 Value::Array(a) => a.iter().for_each(|c| deep_scan(inner, c, root, out)),
263 Value::Object(o) => o.values().for_each(|c| deep_scan(inner, c, root, out)),
264 _ => {}
265 }
266}
267
268fn resolve_index(a: &[Value], i: i64) -> Option<&Value> {
269 let idx = if i < 0 { a.len() as i64 + i } else { i };
270 if idx < 0 {
271 None
272 } else {
273 a.get(idx as usize)
274 }
275}
276
277fn operand_value<'a>(op: &'a Operand, item: &'a Value, root: &'a Value) -> Option<Value> {
278 match op {
279 Operand::Literal(v) => Some(v.clone()),
280 Operand::Current(segs) => select(segs, item, root).first().map(|v| (*v).clone()),
281 Operand::Root(segs) => select(segs, root, root).first().map(|v| (*v).clone()),
282 }
283}
284
285fn eval_filter(expr: &FilterExpr, item: &Value, root: &Value) -> bool {
286 match expr {
287 FilterExpr::Or(a, b) => eval_filter(a, item, root) || eval_filter(b, item, root),
288 FilterExpr::And(a, b) => eval_filter(a, item, root) && eval_filter(b, item, root),
289 FilterExpr::Not(a) => !eval_filter(a, item, root),
290 FilterExpr::Exists(op) => operand_value(op, item, root).is_some(),
291 FilterExpr::Compare(l, op, r) => {
292 let (Some(l), Some(r)) = (operand_value(l, item, root), operand_value(r, item, root))
293 else {
294 return false;
295 };
296 compare(&l, *op, &r)
297 }
298 }
299}
300
301fn compare(l: &Value, op: CmpOp, r: &Value) -> bool {
302 use std::cmp::Ordering;
303 let ord: Option<Ordering> = match (l, r) {
304 (Value::Number(a), Value::Number(b)) => a
305 .as_f64()
306 .zip(b.as_f64())
307 .and_then(|(a, b)| a.partial_cmp(&b)),
308 (Value::String(a), Value::String(b)) => Some(a.cmp(b)),
309 _ => None,
310 };
311 match op {
312 CmpOp::Eq => ord.map(|o| o == Ordering::Equal).unwrap_or(l == r),
313 CmpOp::Ne => ord.map(|o| o != Ordering::Equal).unwrap_or(l != r),
314 CmpOp::Lt => ord == Some(Ordering::Less),
315 CmpOp::Le => matches!(ord, Some(Ordering::Less | Ordering::Equal)),
316 CmpOp::Gt => ord == Some(Ordering::Greater),
317 CmpOp::Ge => matches!(ord, Some(Ordering::Greater | Ordering::Equal)),
318 }
319}
320
321struct Parser {
322 chars: Vec<char>,
323 pos: usize,
324}
325
326impl Parser {
327 fn peek(&self) -> Option<char> {
328 self.chars.get(self.pos).copied()
329 }
330
331 fn peek_at(&self, off: usize) -> Option<char> {
332 self.chars.get(self.pos + off).copied()
333 }
334
335 fn err<T>(&self, msg: &str) -> Result<T, PathError> {
336 Err(PathError::Invalid(format!(
337 "{msg} at position {} in path {}",
338 self.pos,
339 self.chars.iter().collect::<String>()
340 )))
341 }
342
343 fn skip_ws(&mut self) {
344 while self.peek().is_some_and(char::is_whitespace) {
345 self.pos += 1;
346 }
347 }
348
349 fn segments(&mut self, in_filter: bool) -> Result<Vec<Segment>, PathError> {
352 let mut out = Vec::new();
353 loop {
354 match self.peek() {
355 Some('.') if self.peek_at(1) == Some('.') => {
356 self.pos += 2;
357 let inner = match self.peek() {
358 Some('[') => self.bracket()?,
359 Some('*') => {
360 self.pos += 1;
361 Segment::Wildcard
362 }
363 _ => Segment::Names(vec![self.dot_name()?]),
364 };
365 out.push(Segment::DeepScan(Box::new(inner)));
366 }
367 Some('.') => {
368 self.pos += 1;
369 if self.peek() == Some('*') {
370 self.pos += 1;
371 out.push(Segment::Wildcard);
372 } else {
373 out.push(Segment::Names(vec![self.dot_name()?]));
374 }
375 }
376 Some('[') => {
377 let seg = self.bracket()?;
378 out.push(seg);
379 }
380 None => break,
381 Some(_) if in_filter => break,
382 Some(_) => return self.err("Unexpected character"),
383 }
384 }
385 Ok(out)
386 }
387
388 fn dot_name(&mut self) -> Result<String, PathError> {
389 let start = self.pos;
390 while let Some(c) = self.peek() {
391 if c == '.' || c == '[' || c.is_whitespace() || "]()=!<>&|,'\"".contains(c) {
392 break;
393 }
394 self.pos += 1;
395 }
396 if self.pos == start {
397 return self.err("Expected a property name");
398 }
399 Ok(self.chars[start..self.pos].iter().collect())
400 }
401
402 fn quoted(&mut self) -> Result<String, PathError> {
403 let quote = self.peek().unwrap_or('\'');
404 self.pos += 1;
405 let mut s = String::new();
406 loop {
407 match self.peek() {
408 None => return self.err("Unterminated quoted string"),
409 Some('\\') => {
410 self.pos += 1;
411 match self.peek() {
412 Some(c) => {
413 s.push(c);
414 self.pos += 1;
415 }
416 None => return self.err("Unterminated escape"),
417 }
418 }
419 Some(c) if c == quote => {
420 self.pos += 1;
421 return Ok(s);
422 }
423 Some(c) => {
424 s.push(c);
425 self.pos += 1;
426 }
427 }
428 }
429 }
430
431 fn integer(&mut self) -> Option<i64> {
432 let start = self.pos;
433 if self.peek() == Some('-') {
434 self.pos += 1;
435 }
436 while self.peek().is_some_and(|c| c.is_ascii_digit()) {
437 self.pos += 1;
438 }
439 let s: String = self.chars[start..self.pos].iter().collect();
440 match s.parse() {
441 Ok(n) => Some(n),
442 Err(_) => {
443 self.pos = start;
444 None
445 }
446 }
447 }
448
449 fn bracket(&mut self) -> Result<Segment, PathError> {
450 self.pos += 1; self.skip_ws();
452 let seg = match self.peek() {
453 Some('*') => {
454 self.pos += 1;
455 Segment::Wildcard
456 }
457 Some('?') => {
458 self.pos += 1;
459 self.skip_ws();
460 if self.peek() != Some('(') {
461 return self.err("Expected '(' after '?'");
462 }
463 self.pos += 1;
464 let expr = self.filter_or()?;
465 self.skip_ws();
466 if self.peek() != Some(')') {
467 return self.err("Expected ')' closing filter");
468 }
469 self.pos += 1;
470 Segment::Filter(expr)
471 }
472 Some('\'') | Some('"') => {
473 let mut names = vec![self.quoted()?];
474 loop {
475 self.skip_ws();
476 if self.peek() != Some(',') {
477 break;
478 }
479 self.pos += 1;
480 self.skip_ws();
481 if !matches!(self.peek(), Some('\'') | Some('"')) {
482 return self.err("Expected a quoted property name");
483 }
484 names.push(self.quoted()?);
485 }
486 Segment::Names(names)
487 }
488 Some(':') => {
489 self.pos += 1;
490 self.skip_ws();
491 let end = self.integer();
492 Segment::Slice(None, end)
493 }
494 Some(c) if c == '-' || c.is_ascii_digit() => {
495 let first = match self.integer() {
496 Some(n) => n,
497 None => return self.err("Invalid array index"),
498 };
499 self.skip_ws();
500 if self.peek() == Some(':') {
501 self.pos += 1;
502 self.skip_ws();
503 let end = self.integer();
504 Segment::Slice(Some(first), end)
505 } else {
506 let mut idx = vec![first];
507 loop {
508 self.skip_ws();
509 if self.peek() != Some(',') {
510 break;
511 }
512 self.pos += 1;
513 self.skip_ws();
514 match self.integer() {
515 Some(n) => idx.push(n),
516 None => return self.err("Invalid array index"),
517 }
518 }
519 Segment::Indexes(idx)
520 }
521 }
522 _ => return self.err("Invalid bracket expression"),
523 };
524 self.skip_ws();
525 if self.peek() != Some(']') {
526 return self.err("Expected ']'");
527 }
528 self.pos += 1;
529 Ok(seg)
530 }
531
532 fn filter_or(&mut self) -> Result<FilterExpr, PathError> {
533 let mut left = self.filter_and()?;
534 loop {
535 self.skip_ws();
536 if self.peek() == Some('|') && self.peek_at(1) == Some('|') {
537 self.pos += 2;
538 let right = self.filter_and()?;
539 left = FilterExpr::Or(Box::new(left), Box::new(right));
540 } else {
541 return Ok(left);
542 }
543 }
544 }
545
546 fn filter_and(&mut self) -> Result<FilterExpr, PathError> {
547 let mut left = self.filter_unary()?;
548 loop {
549 self.skip_ws();
550 if self.peek() == Some('&') && self.peek_at(1) == Some('&') {
551 self.pos += 2;
552 let right = self.filter_unary()?;
553 left = FilterExpr::And(Box::new(left), Box::new(right));
554 } else {
555 return Ok(left);
556 }
557 }
558 }
559
560 fn filter_unary(&mut self) -> Result<FilterExpr, PathError> {
561 self.skip_ws();
562 if self.peek() == Some('!') && self.peek_at(1) != Some('=') {
563 self.pos += 1;
564 return Ok(FilterExpr::Not(Box::new(self.filter_unary()?)));
565 }
566 if self.peek() == Some('(') {
567 self.pos += 1;
568 let inner = self.filter_or()?;
569 self.skip_ws();
570 if self.peek() != Some(')') {
571 return self.err("Expected ')'");
572 }
573 self.pos += 1;
574 return Ok(inner);
575 }
576 let left = self.operand()?;
577 self.skip_ws();
578 let op = match (self.peek(), self.peek_at(1)) {
579 (Some('='), Some('=')) => Some((CmpOp::Eq, 2)),
580 (Some('!'), Some('=')) => Some((CmpOp::Ne, 2)),
581 (Some('<'), Some('=')) => Some((CmpOp::Le, 2)),
582 (Some('>'), Some('=')) => Some((CmpOp::Ge, 2)),
583 (Some('<'), _) => Some((CmpOp::Lt, 1)),
584 (Some('>'), _) => Some((CmpOp::Gt, 1)),
585 _ => None,
586 };
587 match op {
588 Some((op, len)) => {
589 self.pos += len;
590 let right = self.operand()?;
591 Ok(FilterExpr::Compare(left, op, right))
592 }
593 None => match left {
594 Operand::Literal(_) => self.err("A filter needs a path or a comparison"),
595 path => Ok(FilterExpr::Exists(path)),
596 },
597 }
598 }
599
600 fn operand(&mut self) -> Result<Operand, PathError> {
601 self.skip_ws();
602 match self.peek() {
603 Some('@') => {
604 self.pos += 1;
605 Ok(Operand::Current(self.segments(true)?))
606 }
607 Some('$') => {
608 self.pos += 1;
609 Ok(Operand::Root(self.segments(true)?))
610 }
611 Some('\'') | Some('"') => Ok(Operand::Literal(Value::String(self.quoted()?))),
612 Some(_) => {
613 let start = self.pos;
614 while self
615 .peek()
616 .is_some_and(|c| c.is_ascii_alphanumeric() || "+-.".contains(c))
617 {
618 self.pos += 1;
619 }
620 let word: String = self.chars[start..self.pos].iter().collect();
621 let lit = match word.as_str() {
622 "true" => Value::Bool(true),
623 "false" => Value::Bool(false),
624 "null" => Value::Null,
625 w => {
626 if let Ok(i) = w.parse::<i64>() {
627 Value::Number(Number::from(i))
628 } else if let Some(n) = w.parse::<f64>().ok().and_then(Number::from_f64) {
629 Value::Number(n)
630 } else {
631 return self.err("Invalid filter literal");
632 }
633 }
634 };
635 Ok(Operand::Literal(lit))
636 }
637 None => self.err("Unexpected end of filter"),
638 }
639 }
640}
641
642#[cfg(test)]
643mod tests {
644 use super::*;
645 use serde_json::json;
646
647 fn doc() -> Value {
648 json!({
649 "store": {
650 "book": [
651 {"category": "ref", "author": "Nigel", "price": 8.95},
652 {"category": "fiction", "author": "Evelyn", "price": 12.99, "isbn": "x"},
653 {"category": "fiction", "author": "Herman", "price": 8.99, "isbn": "y"}
654 ],
655 "bicycle": {"color": "red", "price": 19.95}
656 },
657 "weird key": 1,
658 "n": null
659 })
660 }
661
662 #[test]
663 fn definite_paths() {
664 let d = doc();
665 assert_eq!(evaluate(&d, "$").unwrap(), d);
666 assert_eq!(evaluate(&d, "$.store.bicycle.color").unwrap(), json!("red"));
667 assert_eq!(
668 evaluate(&d, "$['store']['bicycle']['color']").unwrap(),
669 json!("red")
670 );
671 assert_eq!(evaluate(&d, "$[\"weird key\"]").unwrap(), json!(1));
672 assert_eq!(
673 evaluate(&d, "$.store.book[0].author").unwrap(),
674 json!("Nigel")
675 );
676 assert_eq!(
677 evaluate(&d, "$.store.book[-1].author").unwrap(),
678 json!("Herman")
679 );
680 assert_eq!(evaluate(&d, "$.n").unwrap(), Value::Null);
682 }
683
684 #[test]
685 fn definite_miss_is_no_results() {
686 let d = doc();
687 assert_eq!(
688 evaluate(&d, "$.store.missing").unwrap_err(),
689 PathError::NoResults("$['store']['missing']".into())
690 );
691 assert!(matches!(
692 evaluate(&d, "$.store.book[9]"),
693 Err(PathError::NoResults(_))
694 ));
695 assert!(matches!(
696 evaluate(&d, "$.store.bicycle.color.x"),
697 Err(PathError::NoResults(_))
698 ));
699 }
700
701 #[test]
702 fn indefinite_paths_return_arrays() {
703 let d = doc();
704 assert_eq!(
705 evaluate(&d, "$.store.book[*].author").unwrap(),
706 json!(["Nigel", "Evelyn", "Herman"])
707 );
708 assert_eq!(
709 evaluate(&d, "$.store.book[0,2].price").unwrap(),
710 json!([8.95, 8.99])
711 );
712 assert_eq!(
713 evaluate(&d, "$.store.book[1:].author").unwrap(),
714 json!(["Evelyn", "Herman"])
715 );
716 assert_eq!(
717 evaluate(&d, "$.store.book[:1].author").unwrap(),
718 json!(["Nigel"])
719 );
720 assert_eq!(
721 evaluate(&d, "$.store.book[-2:].author").unwrap(),
722 json!(["Evelyn", "Herman"])
723 );
724 assert_eq!(evaluate(&d, "$..color").unwrap(), json!(["red"]));
725 assert_eq!(
726 evaluate(&d, "$.store..price")
727 .unwrap()
728 .as_array()
729 .unwrap()
730 .len(),
731 4
732 );
733 assert_eq!(evaluate(&d, "$.store.book[*].nope").unwrap(), json!([]));
735 assert_eq!(
736 evaluate(&d, "$['store']['book','bicycle']")
737 .unwrap()
738 .as_array()
739 .unwrap()
740 .len(),
741 2
742 );
743 }
744
745 #[test]
746 fn filters() {
747 let d = doc();
748 assert_eq!(
749 evaluate(&d, "$.store.book[?(@.price < 10)].author").unwrap(),
750 json!(["Nigel", "Herman"])
751 );
752 assert_eq!(
753 evaluate(&d, "$.store.book[?(@.isbn)].author").unwrap(),
754 json!(["Evelyn", "Herman"])
755 );
756 assert_eq!(
757 evaluate(
758 &d,
759 "$.store.book[?(@.category == 'fiction' && @.price > 10)].author"
760 )
761 .unwrap(),
762 json!(["Evelyn"])
763 );
764 assert_eq!(
765 evaluate(
766 &d,
767 "$.store.book[?(@.author == \"Nigel\" || @.price >= 12.99)].author"
768 )
769 .unwrap(),
770 json!(["Nigel", "Evelyn"])
771 );
772 assert_eq!(
773 evaluate(&d, "$.store.book[?(@.price > $.store.bicycle.price)]").unwrap(),
774 json!([])
775 );
776 assert_eq!(
777 evaluate(&d, "$.store.book[?(!(@.isbn))].author").unwrap(),
778 json!(["Nigel"])
779 );
780 }
781
782 #[test]
783 fn invalid_paths() {
784 for p in [
785 "", "foo", "$.", "$[", "$.a[", "$.x[é]", "$[]", "$.a]", "$['a",
786 ] {
787 assert!(
788 matches!(JsonPath::parse(p), Err(PathError::Invalid(_))),
789 "{p}"
790 );
791 }
792 }
793
794 #[test]
795 fn definiteness() {
796 assert!(JsonPath::parse("$.a[0]['b']").unwrap().is_definite());
797 assert!(JsonPath::parse("$.a[-1]").unwrap().is_definite());
798 assert!(!JsonPath::parse("$.a[*]").unwrap().is_definite());
799 assert!(!JsonPath::parse("$..a").unwrap().is_definite());
800 assert!(!JsonPath::parse("$.a[0,1]").unwrap().is_definite());
801 assert!(!JsonPath::parse("$.a[0:1]").unwrap().is_definite());
802 }
803}