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::QueryPath;
5use serde_json::Value;
6use std::borrow::Cow;
7use std::fmt::Debug;
8
9/// A trait that abstracts JSON-like data structures for JSONPath queries
10///
11/// This trait provides the essential operations needed to traverse and query
12/// hierarchical data structures in a JSONPath-compatible way. Implementors of
13/// this trait can be used with the JSONPath query engine.
14///
15/// The trait requires several standard type conversions to be implemented to
16/// ensure that query operations can properly handle various data types.
17///
18/// # Type Requirements
19///
20/// Implementing types must satisfy these trait bounds:
21/// - `Default`: Provides a default value for the type
22/// - `Clone`: Allows creation of copies of values
23/// - `Debug`: Enables debug formatting
24/// - `From<&str>`: Conversion from string slices
25/// - `From<bool>`: Conversion from boolean values
26/// - `From<i64>`: Conversion from 64-bit integers
27/// - `From<f64>`: Conversion from 64-bit floating point values
28/// - `From<Vec<Self>>`: Conversion from vectors of the same type
29/// - `From<String>`: Conversion from owned strings
30/// - `PartialEq`: Allows equality comparisons
31///
32/// # Examples
33///
34/// The trait is primarily implemented for `serde_json::Value` to enable
35/// JSONPath queries on JSON data structures:
36///
37/// ```
38/// use serde_json::json;
39/// use jsonpath_rust::JsonPath;
40///
41/// let data = json!({
42///     "store": {
43///         "books": [
44///             {"title": "Book 1", "price": 10},
45///             {"title": "Book 2", "price": 15}
46///         ]
47///     }
48/// });
49///
50/// // Access data using the Queryable trait
51/// let books = data.query("$.store.books[*].title").expect("no errors");
52/// ```
53pub trait Queryable
54where
55    Self: Default
56        + Clone
57        + Debug
58        + for<'a> From<&'a str>
59        + From<bool>
60        + From<i64>
61        + From<f64>
62        + From<Vec<Self>>
63        + From<String>
64        + PartialEq,
65{
66    /// Retrieves a reference to the value associated with the given key.
67    /// It is the responsibility of the implementation to handle enclosing single and double quotes.
68    /// The key will be normalized (quotes trimmed, whitespace handled, the escape symbols handled) before lookup.
69    fn get(&self, key: &str) -> Option<&Self>;
70
71    fn as_array(&self) -> Option<&Vec<Self>>;
72
73    fn as_object(&self) -> Option<Vec<(&String, &Self)>>;
74
75    fn as_str(&self) -> Option<&str>;
76
77    fn as_i64(&self) -> Option<i64>;
78    fn as_f64(&self) -> Option<f64>;
79    fn as_bool(&self) -> Option<bool>;
80
81    /// Returns a null value.
82    fn null() -> Self;
83
84    fn extension_custom(_name: &str, _args: Vec<Cow<Self>>) -> Self {
85        Self::null()
86    }
87
88    /// Retrieves a reference to the element at the specified path.
89    /// The path is specified as a string and can be obtained from the query.
90    ///
91    /// # Arguments
92    /// * `path` -  A json path to the element specified as a string (root, field, index only).
93    fn reference<T>(&self, _path: T) -> Option<&Self>
94    where
95        T: Into<QueryPath>,
96    {
97        None
98    }
99
100    /// Retrieves a mutable reference to the element at the specified path.
101    ///
102    /// # Arguments
103    /// * `path` -  A json path to the element specified as a string (root, field, index only).
104    ///
105    /// # Examples
106    ///
107    /// ```
108    /// use serde_json::json;
109    /// use jsonpath_rust::JsonPath;
110    /// use jsonpath_rust::query::queryable::Queryable;
111    /// let mut json = json!({
112    ///             "a": {
113    ///                 "b": {
114    ///                     "c": 42
115    ///                 }
116    ///             }
117    ///         });
118    ///         if let Some(path) = json.query_only_path("$.a.b.c").unwrap().first() {
119    ///             if let Some(v) = json.reference_mut("$.a.b.c") {
120    ///                 *v = json!(43);
121    ///             }
122    ///
123    ///             assert_eq!(
124    ///                 json,
125    ///                 json!({
126    ///                     "a": {
127    ///                         "b": {
128    ///                             "c": 43
129    ///                         }
130    ///                     }
131    ///                 })
132    ///             );
133    /// }
134    //// ```
135    fn reference_mut<T>(&mut self, _path: T) -> Option<&mut Self>
136    where
137        T: Into<QueryPath>,
138    {
139        None
140    }
141}
142
143impl Queryable for Value {
144    fn get(&self, key: &str) -> Option<&Self> {
145        let key = if key.starts_with("'") && key.ends_with("'") {
146            key.trim_matches(|c| c == '\'')
147        } else if key.starts_with('"') && key.ends_with('"') {
148            key.trim_matches(|c| c == '"')
149        } else {
150            key
151        };
152        self.get(key)
153    }
154
155    fn as_array(&self) -> Option<&Vec<Self>> {
156        self.as_array()
157    }
158
159    fn as_object(&self) -> Option<Vec<(&String, &Self)>> {
160        self.as_object()
161            .map(|v| v.into_iter().map(|(k, v)| (k, v)).collect())
162    }
163
164    fn as_str(&self) -> Option<&str> {
165        self.as_str()
166    }
167
168    fn as_i64(&self) -> Option<i64> {
169        self.as_i64()
170    }
171
172    fn as_f64(&self) -> Option<f64> {
173        self.as_f64()
174    }
175
176    fn as_bool(&self) -> Option<bool> {
177        self.as_bool()
178    }
179
180    fn null() -> Self {
181        Value::Null
182    }
183
184    /// Custom extension function for JSONPath queries.
185    ///
186    /// This function allows for custom operations to be performed on JSON data
187    /// based on the provided `name` and `args`.
188    ///
189    /// # Arguments
190    ///
191    /// * `name` - A string slice that holds the name of the custom function.
192    /// * `args` - A vector of `Cow<Self>` that holds the arguments for the custom function.
193    ///
194    /// # Returns
195    ///
196    /// Returns a `Self` value which is the result of the custom function. If the function
197    /// name is not recognized, it returns `Self::null()`.
198    ///
199    /// # Custom Functions
200    ///
201    /// * `"in"` - Checks if the first argument is in the array provided as the second argument.
202    ///   Example: `$.elems[?in(@, $.list)]` - Returns elements from $.elems that are present in $.list
203    ///
204    /// * `"nin"` - Checks if the first argument is not in the array provided as the second argument.
205    ///   Example: `$.elems[?nin(@, $.list)]` - Returns elements from $.elems that are not present in $.list
206    ///
207    /// * `"none_of"` - Checks if none of the elements in the first array are in the second array.
208    ///   Example: `$.elems[?none_of(@, $.list)]` - Returns arrays from $.elems that have no elements in common with $.list
209    ///
210    /// * `"any_of"` - Checks if any of the elements in the first array are in the second array.
211    ///   Example: `$.elems[?any_of(@, $.list)]` - Returns arrays from $.elems that have at least one element in common with $.list
212    ///
213    /// * `"subset_of"` - Checks if all elements in the first array are in the second array.
214    ///   Example: `$.elems[?subset_of(@, $.list)]` - Returns arrays from $.elems where all elements are present in $.list
215    fn extension_custom(name: &str, args: Vec<Cow<Self>>) -> Self {
216        match name {
217            "in" => match args.as_slice() {
218                [lhs, rhs] => match rhs.as_array() {
219                    Some(elements) => elements.iter().any(|item| item == lhs.as_ref()).into(),
220                    None => Self::null(),
221                },
222                _ => Self::null(),
223            },
224            "nin" => match args.as_slice() {
225                [lhs, rhs] => match rhs.as_array() {
226                    Some(elements) => (!elements.iter().any(|item| item == lhs.as_ref())).into(),
227                    None => Self::null(),
228                },
229                _ => Self::null(),
230            },
231            "none_of" => match args.as_slice() {
232                [lhs, rhs] => match (lhs.as_array(), rhs.as_array()) {
233                    (Some(lhs_arr), Some(rhs_arr)) => lhs_arr
234                        .iter()
235                        .all(|lhs| !rhs_arr.iter().any(|rhs| lhs == rhs))
236                        .into(),
237                    _ => Self::null(),
238                },
239                _ => Self::null(),
240            },
241            "any_of" => match args.as_slice() {
242                [lhs, rhs] => match (lhs.as_array(), rhs.as_array()) {
243                    (Some(lhs_arr), Some(rhs_arr)) => lhs_arr
244                        .iter()
245                        .any(|lhs| rhs_arr.iter().any(|rhs| lhs == rhs))
246                        .into(),
247                    _ => Self::null(),
248                },
249                _ => Self::null(),
250            },
251            "subset_of" => match args.as_slice() {
252                [lhs, rhs] => match (lhs.as_array(), rhs.as_array()) {
253                    (Some(lhs_arr), Some(rhs_arr)) => lhs_arr
254                        .iter()
255                        .all(|lhs| rhs_arr.iter().any(|rhs| lhs == rhs))
256                        .into(),
257                    _ => Self::null(),
258                },
259                _ => Self::null(),
260            },
261            _ => Self::null(),
262        }
263    }
264
265    fn reference<T>(&self, path: T) -> Option<&Self>
266    where
267        T: Into<QueryPath>,
268    {
269        convert_js_path(&path.into())
270            .ok()
271            .and_then(|p| self.pointer(p.as_str()))
272    }
273
274    fn reference_mut<T>(&mut self, path: T) -> Option<&mut Self>
275    where
276        T: Into<QueryPath>,
277    {
278        convert_js_path(&path.into())
279            .ok()
280            .and_then(|p| self.pointer_mut(p.as_str()))
281    }
282}
283
284fn convert_js_path(path: &str) -> Parsed<String> {
285    let JpQuery { segments } = parse_json_path(path)?;
286
287    let mut path = String::new();
288    for segment in segments {
289        match segment {
290            Segment::Selector(Selector::Name(name)) => {
291                path.push_str(&format!("/{}", name.trim_matches(|c| c == '\'')));
292            }
293            Segment::Selector(Selector::Index(index)) => {
294                path.push_str(&format!("/{}", index));
295            }
296            s => {
297                return Err(JsonPathError::InvalidJsonPath(format!(
298                    "Invalid segment: {:?}",
299                    s
300                )));
301            }
302        }
303    }
304    Ok(path)
305}
306
307#[cfg(test)]
308mod tests {
309    use crate::parser::Parsed;
310    use crate::query::queryable::{convert_js_path, Queryable};
311    use crate::query::Queried;
312    use crate::JsonPath;
313    use serde_json::json;
314
315    #[test]
316    fn in_smoke() -> Queried<()> {
317        let json = json!({
318            "elems": ["test", "t1", "t2"],
319            "list": ["test", "test2", "test3"],
320        });
321
322        let res = json.query("$.elems[?in(@, $.list)]")?;
323
324        assert_eq!(res, [&json!("test")]);
325
326        Ok(())
327    }
328    #[test]
329    fn nin_smoke() -> Queried<()> {
330        let json = json!({
331            "elems": ["test", "t1", "t2"],
332            "list": ["test", "test2", "test3"],
333        });
334
335        let res = json.query("$.elems[?nin(@, $.list)]")?;
336
337        assert_eq!(res, [&json!("t1"), &json!("t2")]);
338
339        Ok(())
340    }
341    #[test]
342    fn none_of_smoke() -> Queried<()> {
343        let json = json!({
344            "elems": [  ["t1", "_"], ["t2", "t5"], ["t4"]],
345            "list": ["t1","t2", "t3"],
346        });
347
348        let res = json.query("$.elems[?none_of(@, $.list)]")?;
349
350        assert_eq!(res, [&json!(["t4"])]);
351
352        Ok(())
353    }
354    #[test]
355    fn any_of_smoke() -> Queried<()> {
356        let json = json!({
357            "elems": [  ["t1", "_"], ["t4", "t5"], ["t4"]],
358            "list": ["t1","t2", "t3"],
359        });
360
361        let res = json.query("$.elems[?any_of(@, $.list)]")?;
362
363        assert_eq!(res, [&json!(["t1", "_"])]);
364
365        Ok(())
366    }
367    #[test]
368    fn subset_of_smoke() -> Queried<()> {
369        let json = json!({
370            "elems": [  ["t1", "t2"], ["t4", "t5"], ["t6"]],
371            "list": ["t1","t2", "t3"],
372        });
373
374        let res = json.query("$.elems[?subset_of(@, $.list)]")?;
375
376        assert_eq!(res, [&json!(["t1", "t2"])]);
377
378        Ok(())
379    }
380
381    #[test]
382    fn convert_paths() -> Parsed<()> {
383        let r = convert_js_path("$.a.b[2]")?;
384        assert_eq!(r, "/a/b/2");
385
386        Ok(())
387    }
388
389    #[test]
390    fn test_references() -> Parsed<()> {
391        let mut json = json!({
392            "a": {
393                "b": {
394                    "c": 42
395                }
396            }
397        });
398
399        let r = convert_js_path("$.a.b.c")?;
400
401        if let Some(v) = json.pointer_mut(r.as_str()) {
402            *v = json!(43);
403        }
404
405        assert_eq!(
406            json,
407            json!({
408                "a": {
409                    "b": {
410                        "c": 43
411                    }
412                }
413            })
414        );
415
416        Ok(())
417    }
418    #[test]
419    fn test_js_reference() -> Parsed<()> {
420        let mut json = json!({
421            "a": {
422                "b": {
423                    "c": 42
424                }
425            }
426        });
427
428        if let Some(path) = json.query_only_path("$.a.b.c")?.first() {
429            if let Some(v) = json.reference_mut(path) {
430                *v = json!(43);
431            }
432
433            assert_eq!(
434                json,
435                json!({
436                    "a": {
437                        "b": {
438                            "c": 43
439                        }
440                    }
441                })
442            );
443        } else {
444            panic!("no path found");
445        }
446
447        Ok(())
448    }
449}