Skip to main content

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    /// The key is the member name itself: the parser has already removed the quotes
69    /// and replaced the escape sequences (RFC 9535, section 2.3.1.2).
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        self.get(key)
180    }
181
182    fn as_array(&self) -> Option<&Vec<Self>> {
183        self.as_array()
184    }
185
186    fn as_object(&self) -> Option<Vec<(&str, &Self)>> {
187        self.as_object()
188            .map(|v| v.into_iter().map(|(k, v)| (k.as_str(), v)).collect())
189    }
190
191    fn as_str(&self) -> Option<&str> {
192        self.as_str()
193    }
194
195    fn as_i64(&self) -> Option<i64> {
196        self.as_i64()
197    }
198
199    fn as_f64(&self) -> Option<f64> {
200        self.as_f64()
201    }
202
203    fn as_bool(&self) -> Option<bool> {
204        self.as_bool()
205    }
206
207    fn null() -> Self {
208        Value::Null
209    }
210
211    /// Custom extension function for JSONPath queries.
212    ///
213    /// This function allows for custom operations to be performed on JSON data
214    /// based on the provided `name` and `args`.
215    ///
216    /// # Arguments
217    ///
218    /// * `name` - A string slice that holds the name of the custom function.
219    /// * `args` - A vector of `Cow<Self>` that holds the arguments for the custom function.
220    ///
221    /// # Returns
222    ///
223    /// Returns a `Self` value which is the result of the custom function. If the function
224    /// name is not recognized, it returns `Self::null()`.
225    ///
226    /// # Custom Functions
227    ///
228    /// * `"in"` - Checks if the first argument is in the array provided as the second argument.
229    ///   Example: `$.elems[?in(@, $.list)]` - Returns elements from $.elems that are present in $.list
230    ///
231    /// * `"nin"` - Checks if the first argument is not in the array provided as the second argument.
232    ///   Example: `$.elems[?nin(@, $.list)]` - Returns elements from $.elems that are not present in $.list
233    ///
234    /// * `"none_of"` - Checks if none of the elements in the first array are in the second array.
235    ///   Example: `$.elems[?none_of(@, $.list)]` - Returns arrays from $.elems that have no elements in common with $.list
236    ///
237    /// * `"any_of"` - Checks if any of the elements in the first array are in the second array.
238    ///   Example: `$.elems[?any_of(@, $.list)]` - Returns arrays from $.elems that have at least one element in common with $.list
239    ///
240    /// * `"subset_of"` - Checks if all elements in the first array are in the second array.
241    ///   Example: `$.elems[?subset_of(@, $.list)]` - Returns arrays from $.elems where all elements are present in $.list
242    fn extension_custom(name: &str, args: Vec<Cow<Self>>) -> Self {
243        match name {
244            "in" => match args.as_slice() {
245                [lhs, rhs] => match rhs.as_array() {
246                    Some(elements) => elements.iter().any(|item| item == lhs.as_ref()).into(),
247                    None => Self::null(),
248                },
249                _ => Self::null(),
250            },
251            "nin" => 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            "none_of" => match args.as_slice() {
259                [lhs, rhs] => match (lhs.as_array(), rhs.as_array()) {
260                    (Some(lhs_arr), Some(rhs_arr)) => lhs_arr
261                        .iter()
262                        .all(|lhs| !rhs_arr.iter().any(|rhs| lhs == rhs))
263                        .into(),
264                    _ => Self::null(),
265                },
266                _ => Self::null(),
267            },
268            "any_of" => match args.as_slice() {
269                [lhs, rhs] => match (lhs.as_array(), rhs.as_array()) {
270                    (Some(lhs_arr), Some(rhs_arr)) => lhs_arr
271                        .iter()
272                        .any(|lhs| rhs_arr.iter().any(|rhs| lhs == rhs))
273                        .into(),
274                    _ => Self::null(),
275                },
276                _ => Self::null(),
277            },
278            "subset_of" => match args.as_slice() {
279                [lhs, rhs] => match (lhs.as_array(), rhs.as_array()) {
280                    (Some(lhs_arr), Some(rhs_arr)) => lhs_arr
281                        .iter()
282                        .all(|lhs| rhs_arr.iter().any(|rhs| lhs == rhs))
283                        .into(),
284                    _ => Self::null(),
285                },
286                _ => Self::null(),
287            },
288            _ => Self::null(),
289        }
290    }
291
292    fn reference<T>(&self, path: T) -> Option<&Self>
293    where
294        T: Into<QueryPath>,
295    {
296        convert_js_path(&path.into())
297            .ok()
298            .and_then(|p| self.pointer(p.as_str()))
299    }
300
301    fn reference_mut<T>(&mut self, path: T) -> Option<&mut Self>
302    where
303        T: Into<QueryPath>,
304    {
305        convert_js_path(&path.into())
306            .ok()
307            .and_then(|p| self.pointer_mut(p.as_str()))
308    }
309
310    fn delete_by_path(&mut self, path: &str) -> Queried<usize> {
311        let mut deletions = Vec::new();
312        for query_path in &self.query_only_path(path)? {
313            if let Some(deletion_info) = parse_deletion_path(query_path)? {
314                deletions.push(deletion_info);
315            }
316        }
317
318        // Sort deletions to handle array indices correctly (delete from end to start)
319        deletions.sort_by(|a, b| {
320            b.path_depth()
321                .cmp(&a.path_depth())
322                .then_with(|| match (a, b) {
323                    (
324                        DeletionInfo::ArrayIndex { index: idx_a, .. },
325                        DeletionInfo::ArrayIndex { index: idx_b, .. },
326                    ) => idx_b.cmp(idx_a),
327                    _ => std::cmp::Ordering::Equal,
328                })
329        });
330
331        // Perform deletions
332        let deleted_count = deletions.iter().try_fold(0, |c, d| {
333            execute_deletion(self, d).map(|deleted| if deleted { c + 1 } else { c })
334        })?;
335
336        Ok(deleted_count)
337    }
338}
339
340#[derive(Debug, Clone)]
341enum DeletionInfo {
342    ObjectField {
343        parent_path: String,
344        field_name: String,
345    },
346    ArrayIndex {
347        parent_path: String,
348        index: usize,
349    },
350    Root,
351}
352
353impl DeletionInfo {
354    fn path_depth(&self) -> usize {
355        match self {
356            DeletionInfo::Root => 0,
357            DeletionInfo::ObjectField { parent_path, .. }
358            | DeletionInfo::ArrayIndex { parent_path, .. } => parent_path.matches('/').count(),
359        }
360    }
361}
362
363/// Encodes a member name as a JSON Pointer reference token (RFC 6901, section 3).
364fn pointer_token(name: &str) -> String {
365    name.replace('~', "~0").replace('/', "~1")
366}
367
368fn parse_deletion_path(query_path: &str) -> Result<Option<DeletionInfo>, JsonPathError> {
369    if query_path == "$" {
370        return Ok(Some(DeletionInfo::Root));
371    }
372
373    let JpQuery { segments } = parse_json_path(query_path)?;
374
375    if segments.is_empty() {
376        return Ok(None);
377    }
378
379    let mut parent_path = String::new();
380    let mut segments_iter = segments.iter().peekable();
381
382    while let Some(segment) = segments_iter.next() {
383        if segments_iter.peek().is_some() {
384            // Not the last segment, add to parent path
385            match segment {
386                Segment::Selector(Selector::Name(name)) => {
387                    parent_path.push_str(&format!("/{}", pointer_token(name)));
388                }
389                Segment::Selector(Selector::Index(index)) => {
390                    parent_path.push_str(&format!("/{}", index));
391                }
392                e => {
393                    return Err(JsonPathError::InvalidJsonPath(format!(
394                        "Unsupported segment to be deleted: {:?}",
395                        e
396                    )));
397                }
398            }
399        } else {
400            match segment {
401                Segment::Selector(Selector::Name(name)) => {
402                    let field_name = name.to_string();
403                    return Ok(Some(DeletionInfo::ObjectField {
404                        parent_path,
405                        field_name,
406                    }));
407                }
408                Segment::Selector(Selector::Index(index)) => {
409                    return Ok(Some(DeletionInfo::ArrayIndex {
410                        parent_path,
411                        index: *index as usize,
412                    }));
413                }
414                e => {
415                    return Err(JsonPathError::InvalidJsonPath(format!(
416                        "Unsupported segment to be deleted: {:?}",
417                        e
418                    )));
419                }
420            }
421        }
422    }
423
424    Ok(None)
425}
426
427fn execute_deletion(value: &mut Value, deletion: &DeletionInfo) -> Queried<bool> {
428    match deletion {
429        DeletionInfo::Root => {
430            *value = Value::Null;
431            Ok(true)
432        }
433        DeletionInfo::ObjectField {
434            parent_path,
435            field_name,
436        } => {
437            let parent = if parent_path.is_empty() {
438                value
439            } else {
440                value.pointer_mut(parent_path).ok_or_else(|| {
441                    JsonPathError::InvalidJsonPath("Parent path not found".to_string())
442                })?
443            };
444
445            if let Some(obj) = parent.as_object_mut() {
446                Ok(obj.remove(field_name).is_some())
447            } else {
448                Err(JsonPathError::InvalidJsonPath(
449                    "Parent is not an object".to_string(),
450                ))
451            }
452        }
453        DeletionInfo::ArrayIndex { parent_path, index } => {
454            let parent = if parent_path.is_empty() {
455                value
456            } else {
457                value.pointer_mut(parent_path).ok_or_else(|| {
458                    JsonPathError::InvalidJsonPath("Parent path not found".to_string())
459                })?
460            };
461
462            if let Some(arr) = parent.as_array_mut() {
463                if *index < arr.len() {
464                    arr.remove(*index);
465                    Ok(true)
466                } else {
467                    Ok(false) // Index out of bounds
468                }
469            } else {
470                Err(JsonPathError::InvalidJsonPath(
471                    "Parent is not an array".to_string(),
472                ))
473            }
474        }
475    }
476}
477
478fn convert_js_path(path: &str) -> Parsed<String> {
479    let JpQuery { segments } = parse_json_path(path)?;
480
481    let mut path = String::new();
482    for segment in segments {
483        match segment {
484            Segment::Selector(Selector::Name(name)) => {
485                path.push_str(&format!("/{}", pointer_token(&name)));
486            }
487            Segment::Selector(Selector::Index(index)) => {
488                path.push_str(&format!("/{}", index));
489            }
490            s => {
491                return Err(JsonPathError::InvalidJsonPath(format!(
492                    "Invalid segment: {:?}",
493                    s
494                )));
495            }
496        }
497    }
498    Ok(path)
499}
500
501#[cfg(test)]
502mod tests {
503    use crate::parser::Parsed;
504    use crate::query::queryable::{convert_js_path, Queryable};
505    use crate::query::Queried;
506    use crate::JsonPath;
507    use serde_json::{json, Value};
508
509    #[test]
510    fn in_smoke() -> Queried<()> {
511        let json = json!({
512            "elems": ["test", "t1", "t2"],
513            "list": ["test", "test2", "test3"],
514        });
515
516        let res = json.query("$.elems[?in(@, $.list)]")?;
517
518        assert_eq!(res, [&json!("test")]);
519
520        Ok(())
521    }
522    #[test]
523    fn nin_smoke() -> Queried<()> {
524        let json = json!({
525            "elems": ["test", "t1", "t2"],
526            "list": ["test", "test2", "test3"],
527        });
528
529        let res = json.query("$.elems[?nin(@, $.list)]")?;
530
531        assert_eq!(res, [&json!("t1"), &json!("t2")]);
532
533        Ok(())
534    }
535    #[test]
536    fn none_of_smoke() -> Queried<()> {
537        let json = json!({
538            "elems": [  ["t1", "_"], ["t2", "t5"], ["t4"]],
539            "list": ["t1","t2", "t3"],
540        });
541
542        let res = json.query("$.elems[?none_of(@, $.list)]")?;
543
544        assert_eq!(res, [&json!(["t4"])]);
545
546        Ok(())
547    }
548    #[test]
549    fn any_of_smoke() -> Queried<()> {
550        let json = json!({
551            "elems": [  ["t1", "_"], ["t4", "t5"], ["t4"]],
552            "list": ["t1","t2", "t3"],
553        });
554
555        let res = json.query("$.elems[?any_of(@, $.list)]")?;
556
557        assert_eq!(res, [&json!(["t1", "_"])]);
558
559        Ok(())
560    }
561    #[test]
562    fn subset_of_smoke() -> Queried<()> {
563        let json = json!({
564            "elems": [  ["t1", "t2"], ["t4", "t5"], ["t6"]],
565            "list": ["t1","t2", "t3"],
566        });
567
568        let res = json.query("$.elems[?subset_of(@, $.list)]")?;
569
570        assert_eq!(res, [&json!(["t1", "t2"])]);
571
572        Ok(())
573    }
574
575    #[test]
576    fn convert_paths() -> Parsed<()> {
577        let r = convert_js_path("$.a.b[2]")?;
578        assert_eq!(r, "/a/b/2");
579
580        Ok(())
581    }
582
583    #[test]
584    fn test_references() -> Parsed<()> {
585        let mut json = json!({
586            "a": {
587                "b": {
588                    "c": 42
589                }
590            }
591        });
592
593        let r = convert_js_path("$.a.b.c")?;
594
595        if let Some(v) = json.pointer_mut(r.as_str()) {
596            *v = json!(43);
597        }
598
599        assert_eq!(
600            json,
601            json!({
602                "a": {
603                    "b": {
604                        "c": 43
605                    }
606                }
607            })
608        );
609
610        Ok(())
611    }
612    #[test]
613    fn test_js_reference() -> Parsed<()> {
614        let mut json = json!({
615            "a": {
616                "b": {
617                    "c": 42
618                }
619            }
620        });
621
622        if let Some(path) = json.query_only_path("$.a.b.c")?.first() {
623            if let Some(v) = json.reference_mut(path) {
624                *v = json!(43);
625            }
626
627            assert_eq!(
628                json,
629                json!({
630                    "a": {
631                        "b": {
632                            "c": 43
633                        }
634                    }
635                })
636            );
637        } else {
638            panic!("no path found");
639        }
640
641        Ok(())
642    }
643    #[test]
644    fn test_delete_object_field() {
645        let mut data = json!({
646            "users": {
647                "alice": {"age": 30},
648                "bob": {"age": 25}
649            }
650        });
651
652        let deleted = data.delete_by_path("$.users.alice").unwrap();
653        assert_eq!(deleted, 1);
654
655        let expected = json!({
656            "users": {
657                "bob": {"age": 25}
658            }
659        });
660        assert_eq!(data, expected);
661    }
662
663    #[test]
664    fn test_delete_array_element() {
665        let mut data = json!({
666            "numbers": [1, 2, 3, 4, 5]
667        });
668
669        let deleted = data.delete_by_path("$.numbers[2]").unwrap();
670        assert_eq!(deleted, 1);
671
672        let expected = json!({
673            "numbers": [1, 2, 4, 5]
674        });
675        assert_eq!(data, expected);
676    }
677
678    #[test]
679    fn test_delete_multiple_elements() {
680        let mut data = json!({
681            "users": [
682                {"name": "Alice", "age": 30},
683                {"name": "Bob", "age": 25},
684                {"name": "Charlie", "age": 35},
685                {"name": "David", "age": 22}
686            ]
687        });
688
689        // Delete users older than 24
690        let deleted = data.delete_by_path("$.users[?(@.age > 24)]").unwrap();
691        assert_eq!(deleted, 3);
692
693        let expected = json!({
694            "users": [
695                {"name": "David", "age": 22}
696            ]
697        });
698        assert_eq!(data, expected);
699    }
700
701    #[test]
702    fn test_delete_nested_fields() {
703        let mut data = json!({
704            "company": {
705                "departments": {
706                    "engineering": {"budget": 100000},
707                    "marketing": {"budget": 50000},
708                    "hr": {"budget": 30000}
709                }
710            }
711        });
712
713        let deleted = data
714            .delete_by_path("$.company.departments.marketing")
715            .unwrap();
716        assert_eq!(deleted, 1);
717
718        let expected = json!({
719            "company": {
720                "departments": {
721                    "engineering": {"budget": 100000},
722                    "hr": {"budget": 30000}
723                }
724            }
725        });
726        assert_eq!(data, expected);
727    }
728
729    #[test]
730    fn test_delete_nonexistent_path() {
731        let mut data = json!({
732            "test": "value"
733        });
734
735        let deleted = data.delete_by_path("$.nonexistent").unwrap();
736        assert_eq!(deleted, 0);
737
738        // Data should remain unchanged
739        let expected = json!({
740            "test": "value"
741        });
742        assert_eq!(data, expected);
743    }
744
745    #[test]
746    fn test_delete_root() {
747        let mut data = json!({
748            "test": "value"
749        });
750
751        let deleted = data.delete_by_path("$").unwrap();
752        assert_eq!(deleted, 1);
753        assert_eq!(data, Value::Null);
754    }
755}