jsonpath_rust/query/
queryable.rs

1use crate::parser::errors::JsonPathError;
2use crate::parser::model::{JpQuery, Segment, Selector};
3use crate::parser::{parse_json_path, Parsed};
4use crate::query::{Queried, QueryPath};
5use crate::JsonPath;
6use serde_json::Value;
7use std::borrow::Cow;
8use std::fmt::Debug;
9
10/// A trait that abstracts JSON-like data structures for JSONPath queries
11///
12/// This trait provides the essential operations needed to traverse and query
13/// hierarchical data structures in a JSONPath-compatible way. Implementors of
14/// this trait can be used with the JSONPath query engine.
15///
16/// The trait requires several standard type conversions to be implemented to
17/// ensure that query operations can properly handle various data types.
18///
19/// # Type Requirements
20///
21/// Implementing types must satisfy these trait bounds:
22/// - `Default`: Provides a default value for the type
23/// - `Clone`: Allows creation of copies of values
24/// - `Debug`: Enables debug formatting
25/// - `From<&str>`: Conversion from string slices
26/// - `From<bool>`: Conversion from boolean values
27/// - `From<i64>`: Conversion from 64-bit integers
28/// - `From<f64>`: Conversion from 64-bit floating point values
29/// - `From<Vec<Self>>`: Conversion from vectors of the same type
30/// - `From<String>`: Conversion from owned strings
31/// - `PartialEq`: Allows equality comparisons
32///
33/// # Examples
34///
35/// The trait is primarily implemented for `serde_json::Value` to enable
36/// JSONPath queries on JSON data structures:
37///
38/// ```
39/// use serde_json::json;
40/// use jsonpath_rust::JsonPath;
41///
42/// let data = json!({
43///     "store": {
44///         "books": [
45///             {"title": "Book 1", "price": 10},
46///             {"title": "Book 2", "price": 15}
47///         ]
48///     }
49/// });
50///
51/// // Access data using the Queryable trait
52/// let books = data.query("$.store.books[*].title").expect("no errors");
53/// ```
54pub trait Queryable
55where
56    Self: Default
57        + Clone
58        + Debug
59        + for<'a> From<&'a str>
60        + From<bool>
61        + From<i64>
62        + From<f64>
63        + From<Vec<Self>>
64        + From<String>
65        + PartialEq,
66{
67    /// Retrieves a reference to the value associated with the given key.
68    /// It is the responsibility of the implementation to handle enclosing single and double quotes.
69    /// The key will be normalized (quotes trimmed, whitespace handled, the escape symbols handled) before lookup.
70    fn get(&self, key: &str) -> Option<&Self>;
71
72    fn as_array(&self) -> Option<&Vec<Self>>;
73
74    fn as_object(&self) -> Option<Vec<(&str, &Self)>>;
75
76    fn as_str(&self) -> Option<&str>;
77
78    fn as_i64(&self) -> Option<i64>;
79    fn as_f64(&self) -> Option<f64>;
80    fn as_bool(&self) -> Option<bool>;
81
82    /// Returns a null value.
83    fn null() -> Self;
84
85    fn extension_custom(_name: &str, _args: Vec<Cow<Self>>) -> Self {
86        Self::null()
87    }
88
89    /// Retrieves a reference to the element at the specified path.
90    /// The path is specified as a string and can be obtained from the query.
91    ///
92    /// # Arguments
93    /// * `path` -  A json path to the element specified as a string (root, field, index only).
94    fn reference<T>(&self, _path: T) -> Option<&Self>
95    where
96        T: Into<QueryPath>,
97    {
98        None
99    }
100
101    /// Retrieves a mutable reference to the element at the specified path.
102    ///
103    /// # Arguments
104    /// * `path` -  A json path to the element specified as a string (root, field, index only).
105    ///
106    /// # Examples
107    ///
108    /// ```
109    /// use serde_json::json;
110    /// use jsonpath_rust::JsonPath;
111    /// use jsonpath_rust::query::queryable::Queryable;
112    /// let mut json = json!({
113    ///             "a": {
114    ///                 "b": {
115    ///                     "c": 42
116    ///                 }
117    ///             }
118    ///         });
119    ///         if let Some(path) = json.query_only_path("$.a.b.c").unwrap().first() {
120    ///             if let Some(v) = json.reference_mut("$.a.b.c") {
121    ///                 *v = json!(43);
122    ///             }
123    ///
124    ///             assert_eq!(
125    ///                 json,
126    ///                 json!({
127    ///                     "a": {
128    ///                         "b": {
129    ///                             "c": 43
130    ///                         }
131    ///                     }
132    ///                 })
133    ///             );
134    /// }
135    //// ```
136    fn reference_mut<T>(&mut self, _path: T) -> Option<&mut Self>
137    where
138        T: Into<QueryPath>,
139    {
140        None
141    }
142
143    /// Deletes all elements matching the given JSONPath
144    ///
145    /// # Arguments
146    /// * `path` - JSONPath string specifying elements to delete
147    ///
148    /// # Returns
149    /// * `Ok(usize)` - Number of elements deleted
150    /// * `Err(JsonPathError)` - If the path is invalid or deletion fails
151    ///
152    /// # Examples
153    /// ```
154    /// use serde_json::json;
155    /// use jsonpath_rust::JsonPath;
156    /// use crate::jsonpath_rust::query::queryable::Queryable;
157    ///
158    /// let mut data = json!({
159    ///     "users": [
160    ///         {"name": "Alice", "age": 30},
161    ///         {"name": "Bob", "age": 25},
162    ///         {"name": "Charlie", "age": 35}
163    ///     ]
164    /// });
165    ///
166    /// // Delete users older than 30
167    /// let deleted = data.delete_by_path("$.users[?(@.age > 30)]").unwrap();
168    /// assert_eq!(deleted, 1);
169    /// ```
170    fn delete_by_path(&mut self, _path: &str) -> Queried<usize> {
171        Err(JsonPathError::InvalidJsonPath(
172            "Deletion not supported".to_string(),
173        ))
174    }
175}
176
177impl Queryable for Value {
178    fn get(&self, key: &str) -> Option<&Self> {
179        let key = if key.starts_with("'") && key.ends_with("'") {
180            key.trim_matches(|c| c == '\'')
181        } else if key.starts_with('"') && key.ends_with('"') {
182            key.trim_matches(|c| c == '"')
183        } else {
184            key
185        };
186        self.get(key)
187    }
188
189    fn as_array(&self) -> Option<&Vec<Self>> {
190        self.as_array()
191    }
192
193    fn as_object(&self) -> Option<Vec<(&str, &Self)>> {
194        self.as_object()
195            .map(|v| v.into_iter().map(|(k, v)| (k.as_str(), v)).collect())
196    }
197
198    fn as_str(&self) -> Option<&str> {
199        self.as_str()
200    }
201
202    fn as_i64(&self) -> Option<i64> {
203        self.as_i64()
204    }
205
206    fn as_f64(&self) -> Option<f64> {
207        self.as_f64()
208    }
209
210    fn as_bool(&self) -> Option<bool> {
211        self.as_bool()
212    }
213
214    fn null() -> Self {
215        Value::Null
216    }
217
218    /// Custom extension function for JSONPath queries.
219    ///
220    /// This function allows for custom operations to be performed on JSON data
221    /// based on the provided `name` and `args`.
222    ///
223    /// # Arguments
224    ///
225    /// * `name` - A string slice that holds the name of the custom function.
226    /// * `args` - A vector of `Cow<Self>` that holds the arguments for the custom function.
227    ///
228    /// # Returns
229    ///
230    /// Returns a `Self` value which is the result of the custom function. If the function
231    /// name is not recognized, it returns `Self::null()`.
232    ///
233    /// # Custom Functions
234    ///
235    /// * `"in"` - Checks if the first argument is in the array provided as the second argument.
236    ///   Example: `$.elems[?in(@, $.list)]` - Returns elements from $.elems that are present in $.list
237    ///
238    /// * `"nin"` - Checks if the first argument is not in the array provided as the second argument.
239    ///   Example: `$.elems[?nin(@, $.list)]` - Returns elements from $.elems that are not present in $.list
240    ///
241    /// * `"none_of"` - Checks if none of the elements in the first array are in the second array.
242    ///   Example: `$.elems[?none_of(@, $.list)]` - Returns arrays from $.elems that have no elements in common with $.list
243    ///
244    /// * `"any_of"` - Checks if any of the elements in the first array are in the second array.
245    ///   Example: `$.elems[?any_of(@, $.list)]` - Returns arrays from $.elems that have at least one element in common with $.list
246    ///
247    /// * `"subset_of"` - Checks if all elements in the first array are in the second array.
248    ///   Example: `$.elems[?subset_of(@, $.list)]` - Returns arrays from $.elems where all elements are present in $.list
249    fn extension_custom(name: &str, args: Vec<Cow<Self>>) -> Self {
250        match name {
251            "in" => match args.as_slice() {
252                [lhs, rhs] => match rhs.as_array() {
253                    Some(elements) => elements.iter().any(|item| item == lhs.as_ref()).into(),
254                    None => Self::null(),
255                },
256                _ => Self::null(),
257            },
258            "nin" => match args.as_slice() {
259                [lhs, rhs] => match rhs.as_array() {
260                    Some(elements) => (!elements.iter().any(|item| item == lhs.as_ref())).into(),
261                    None => Self::null(),
262                },
263                _ => Self::null(),
264            },
265            "none_of" => match args.as_slice() {
266                [lhs, rhs] => match (lhs.as_array(), rhs.as_array()) {
267                    (Some(lhs_arr), Some(rhs_arr)) => lhs_arr
268                        .iter()
269                        .all(|lhs| !rhs_arr.iter().any(|rhs| lhs == rhs))
270                        .into(),
271                    _ => Self::null(),
272                },
273                _ => Self::null(),
274            },
275            "any_of" => match args.as_slice() {
276                [lhs, rhs] => match (lhs.as_array(), rhs.as_array()) {
277                    (Some(lhs_arr), Some(rhs_arr)) => lhs_arr
278                        .iter()
279                        .any(|lhs| rhs_arr.iter().any(|rhs| lhs == rhs))
280                        .into(),
281                    _ => Self::null(),
282                },
283                _ => Self::null(),
284            },
285            "subset_of" => match args.as_slice() {
286                [lhs, rhs] => match (lhs.as_array(), rhs.as_array()) {
287                    (Some(lhs_arr), Some(rhs_arr)) => lhs_arr
288                        .iter()
289                        .all(|lhs| rhs_arr.iter().any(|rhs| lhs == rhs))
290                        .into(),
291                    _ => Self::null(),
292                },
293                _ => Self::null(),
294            },
295            _ => Self::null(),
296        }
297    }
298
299    fn reference<T>(&self, path: T) -> Option<&Self>
300    where
301        T: Into<QueryPath>,
302    {
303        convert_js_path(&path.into())
304            .ok()
305            .and_then(|p| self.pointer(p.as_str()))
306    }
307
308    fn reference_mut<T>(&mut self, path: T) -> Option<&mut Self>
309    where
310        T: Into<QueryPath>,
311    {
312        convert_js_path(&path.into())
313            .ok()
314            .and_then(|p| self.pointer_mut(p.as_str()))
315    }
316
317    fn delete_by_path(&mut self, path: &str) -> Queried<usize> {
318        let mut deletions = Vec::new();
319        for query_path in &self.query_only_path(path)? {
320            if let Some(deletion_info) = parse_deletion_path(query_path)? {
321                deletions.push(deletion_info);
322            }
323        }
324
325        // Sort deletions to handle array indices correctly (delete from end to start)
326        deletions.sort_by(|a, b| {
327            b.path_depth()
328                .cmp(&a.path_depth())
329                .then_with(|| match (a, b) {
330                    (
331                        DeletionInfo::ArrayIndex { index: idx_a, .. },
332                        DeletionInfo::ArrayIndex { index: idx_b, .. },
333                    ) => idx_b.cmp(idx_a),
334                    _ => std::cmp::Ordering::Equal,
335                })
336        });
337
338        // Perform deletions
339        let deleted_count = deletions.iter().try_fold(0, |c, d| {
340            execute_deletion(self, d).map(|deleted| if deleted { c + 1 } else { c })
341        })?;
342
343        Ok(deleted_count)
344    }
345}
346
347#[derive(Debug, Clone)]
348enum DeletionInfo {
349    ObjectField {
350        parent_path: String,
351        field_name: String,
352    },
353    ArrayIndex {
354        parent_path: String,
355        index: usize,
356    },
357    Root,
358}
359
360impl DeletionInfo {
361    fn path_depth(&self) -> usize {
362        match self {
363            DeletionInfo::Root => 0,
364            DeletionInfo::ObjectField { parent_path, .. }
365            | DeletionInfo::ArrayIndex { parent_path, .. } => parent_path.matches('/').count(),
366        }
367    }
368}
369
370fn parse_deletion_path(query_path: &str) -> Result<Option<DeletionInfo>, JsonPathError> {
371    if query_path == "$" {
372        return Ok(Some(DeletionInfo::Root));
373    }
374
375    let JpQuery { segments } = parse_json_path(query_path)?;
376
377    if segments.is_empty() {
378        return Ok(None);
379    }
380
381    let mut parent_path = String::new();
382    let mut segments_iter = segments.iter().peekable();
383
384    while let Some(segment) = segments_iter.next() {
385        if segments_iter.peek().is_some() {
386            // Not the last segment, add to parent path
387            match segment {
388                Segment::Selector(Selector::Name(name)) => {
389                    parent_path.push_str(&format!("/{}", name.trim_matches(|c| c == '\'')));
390                }
391                Segment::Selector(Selector::Index(index)) => {
392                    parent_path.push_str(&format!("/{}", index));
393                }
394                e => {
395                    return Err(JsonPathError::InvalidJsonPath(format!(
396                        "Unsupported segment to be deleted: {:?}",
397                        e
398                    )));
399                }
400            }
401        } else {
402            match segment {
403                Segment::Selector(Selector::Name(name)) => {
404                    let field_name = name.trim_matches(|c| c == '\'').to_string();
405                    return Ok(Some(DeletionInfo::ObjectField {
406                        parent_path,
407                        field_name,
408                    }));
409                }
410                Segment::Selector(Selector::Index(index)) => {
411                    return Ok(Some(DeletionInfo::ArrayIndex {
412                        parent_path,
413                        index: *index as usize,
414                    }));
415                }
416                e => {
417                    return Err(JsonPathError::InvalidJsonPath(format!(
418                        "Unsupported segment to be deleted: {:?}",
419                        e
420                    )));
421                }
422            }
423        }
424    }
425
426    Ok(None)
427}
428
429fn execute_deletion(value: &mut Value, deletion: &DeletionInfo) -> Queried<bool> {
430    match deletion {
431        DeletionInfo::Root => {
432            *value = Value::Null;
433            Ok(true)
434        }
435        DeletionInfo::ObjectField {
436            parent_path,
437            field_name,
438        } => {
439            let parent = if parent_path.is_empty() {
440                value
441            } else {
442                value.pointer_mut(parent_path).ok_or_else(|| {
443                    JsonPathError::InvalidJsonPath("Parent path not found".to_string())
444                })?
445            };
446
447            if let Some(obj) = parent.as_object_mut() {
448                Ok(obj.remove(field_name).is_some())
449            } else {
450                Err(JsonPathError::InvalidJsonPath(
451                    "Parent is not an object".to_string(),
452                ))
453            }
454        }
455        DeletionInfo::ArrayIndex { parent_path, index } => {
456            let parent = if parent_path.is_empty() {
457                value
458            } else {
459                value.pointer_mut(parent_path).ok_or_else(|| {
460                    JsonPathError::InvalidJsonPath("Parent path not found".to_string())
461                })?
462            };
463
464            if let Some(arr) = parent.as_array_mut() {
465                if *index < arr.len() {
466                    arr.remove(*index);
467                    Ok(true)
468                } else {
469                    Ok(false) // Index out of bounds
470                }
471            } else {
472                Err(JsonPathError::InvalidJsonPath(
473                    "Parent is not an array".to_string(),
474                ))
475            }
476        }
477    }
478}
479
480fn convert_js_path(path: &str) -> Parsed<String> {
481    let JpQuery { segments } = parse_json_path(path)?;
482
483    let mut path = String::new();
484    for segment in segments {
485        match segment {
486            Segment::Selector(Selector::Name(name)) => {
487                path.push_str(&format!("/{}", name.trim_matches(|c| c == '\'')));
488            }
489            Segment::Selector(Selector::Index(index)) => {
490                path.push_str(&format!("/{}", index));
491            }
492            s => {
493                return Err(JsonPathError::InvalidJsonPath(format!(
494                    "Invalid segment: {:?}",
495                    s
496                )));
497            }
498        }
499    }
500    Ok(path)
501}
502
503#[cfg(test)]
504mod tests {
505    use crate::parser::Parsed;
506    use crate::query::queryable::{convert_js_path, Queryable};
507    use crate::query::Queried;
508    use crate::JsonPath;
509    use serde_json::{json, Value};
510
511    #[test]
512    fn in_smoke() -> Queried<()> {
513        let json = json!({
514            "elems": ["test", "t1", "t2"],
515            "list": ["test", "test2", "test3"],
516        });
517
518        let res = json.query("$.elems[?in(@, $.list)]")?;
519
520        assert_eq!(res, [&json!("test")]);
521
522        Ok(())
523    }
524    #[test]
525    fn nin_smoke() -> Queried<()> {
526        let json = json!({
527            "elems": ["test", "t1", "t2"],
528            "list": ["test", "test2", "test3"],
529        });
530
531        let res = json.query("$.elems[?nin(@, $.list)]")?;
532
533        assert_eq!(res, [&json!("t1"), &json!("t2")]);
534
535        Ok(())
536    }
537    #[test]
538    fn none_of_smoke() -> Queried<()> {
539        let json = json!({
540            "elems": [  ["t1", "_"], ["t2", "t5"], ["t4"]],
541            "list": ["t1","t2", "t3"],
542        });
543
544        let res = json.query("$.elems[?none_of(@, $.list)]")?;
545
546        assert_eq!(res, [&json!(["t4"])]);
547
548        Ok(())
549    }
550    #[test]
551    fn any_of_smoke() -> Queried<()> {
552        let json = json!({
553            "elems": [  ["t1", "_"], ["t4", "t5"], ["t4"]],
554            "list": ["t1","t2", "t3"],
555        });
556
557        let res = json.query("$.elems[?any_of(@, $.list)]")?;
558
559        assert_eq!(res, [&json!(["t1", "_"])]);
560
561        Ok(())
562    }
563    #[test]
564    fn subset_of_smoke() -> Queried<()> {
565        let json = json!({
566            "elems": [  ["t1", "t2"], ["t4", "t5"], ["t6"]],
567            "list": ["t1","t2", "t3"],
568        });
569
570        let res = json.query("$.elems[?subset_of(@, $.list)]")?;
571
572        assert_eq!(res, [&json!(["t1", "t2"])]);
573
574        Ok(())
575    }
576
577    #[test]
578    fn convert_paths() -> Parsed<()> {
579        let r = convert_js_path("$.a.b[2]")?;
580        assert_eq!(r, "/a/b/2");
581
582        Ok(())
583    }
584
585    #[test]
586    fn test_references() -> Parsed<()> {
587        let mut json = json!({
588            "a": {
589                "b": {
590                    "c": 42
591                }
592            }
593        });
594
595        let r = convert_js_path("$.a.b.c")?;
596
597        if let Some(v) = json.pointer_mut(r.as_str()) {
598            *v = json!(43);
599        }
600
601        assert_eq!(
602            json,
603            json!({
604                "a": {
605                    "b": {
606                        "c": 43
607                    }
608                }
609            })
610        );
611
612        Ok(())
613    }
614    #[test]
615    fn test_js_reference() -> Parsed<()> {
616        let mut json = json!({
617            "a": {
618                "b": {
619                    "c": 42
620                }
621            }
622        });
623
624        if let Some(path) = json.query_only_path("$.a.b.c")?.first() {
625            if let Some(v) = json.reference_mut(path) {
626                *v = json!(43);
627            }
628
629            assert_eq!(
630                json,
631                json!({
632                    "a": {
633                        "b": {
634                            "c": 43
635                        }
636                    }
637                })
638            );
639        } else {
640            panic!("no path found");
641        }
642
643        Ok(())
644    }
645    #[test]
646    fn test_delete_object_field() {
647        let mut data = json!({
648            "users": {
649                "alice": {"age": 30},
650                "bob": {"age": 25}
651            }
652        });
653
654        let deleted = data.delete_by_path("$.users.alice").unwrap();
655        assert_eq!(deleted, 1);
656
657        let expected = json!({
658            "users": {
659                "bob": {"age": 25}
660            }
661        });
662        assert_eq!(data, expected);
663    }
664
665    #[test]
666    fn test_delete_array_element() {
667        let mut data = json!({
668            "numbers": [1, 2, 3, 4, 5]
669        });
670
671        let deleted = data.delete_by_path("$.numbers[2]").unwrap();
672        assert_eq!(deleted, 1);
673
674        let expected = json!({
675            "numbers": [1, 2, 4, 5]
676        });
677        assert_eq!(data, expected);
678    }
679
680    #[test]
681    fn test_delete_multiple_elements() {
682        let mut data = json!({
683            "users": [
684                {"name": "Alice", "age": 30},
685                {"name": "Bob", "age": 25},
686                {"name": "Charlie", "age": 35},
687                {"name": "David", "age": 22}
688            ]
689        });
690
691        // Delete users older than 24
692        let deleted = data.delete_by_path("$.users[?(@.age > 24)]").unwrap();
693        assert_eq!(deleted, 3);
694
695        let expected = json!({
696            "users": [
697                {"name": "David", "age": 22}
698            ]
699        });
700        assert_eq!(data, expected);
701    }
702
703    #[test]
704    fn test_delete_nested_fields() {
705        let mut data = json!({
706            "company": {
707                "departments": {
708                    "engineering": {"budget": 100000},
709                    "marketing": {"budget": 50000},
710                    "hr": {"budget": 30000}
711                }
712            }
713        });
714
715        let deleted = data
716            .delete_by_path("$.company.departments.marketing")
717            .unwrap();
718        assert_eq!(deleted, 1);
719
720        let expected = json!({
721            "company": {
722                "departments": {
723                    "engineering": {"budget": 100000},
724                    "hr": {"budget": 30000}
725                }
726            }
727        });
728        assert_eq!(data, expected);
729    }
730
731    #[test]
732    fn test_delete_nonexistent_path() {
733        let mut data = json!({
734            "test": "value"
735        });
736
737        let deleted = data.delete_by_path("$.nonexistent").unwrap();
738        assert_eq!(deleted, 0);
739
740        // Data should remain unchanged
741        let expected = json!({
742            "test": "value"
743        });
744        assert_eq!(data, expected);
745    }
746
747    #[test]
748    fn test_delete_root() {
749        let mut data = json!({
750            "test": "value"
751        });
752
753        let deleted = data.delete_by_path("$").unwrap();
754        assert_eq!(deleted, 1);
755        assert_eq!(data, Value::Null);
756    }
757}