1 | use crate::parser::errors::JsonPathError;
|
2 | use crate::parser::model::{JpQuery, Segment, Selector};
|
3 | use crate::parser::{parse_json_path, Parsed};
|
4 | use crate::query::QueryPath;
|
5 | use serde_json::Value;
|
6 | use std::borrow::Cow;
|
7 | use 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 | /// ```
|
53 | pub trait Queryable
|
54 | where
|
55 | Self: Default
|
56 | + Clone
|
57 | + Debug
|
58 | + for<'a> dynFrom<&'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 |
|
143 | impl 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 |
|
284 | fn convert_js_path(path: &str) -> Parsed<String> {
|
285 | let JpQuery { segments: Vec } = parse_json_path(jp_str:path)?;
|
286 |
|
287 | let mut path: String = String::new();
|
288 | for segment: Segment in segments {
|
289 | match segment {
|
290 | Segment::Selector(Selector::Name(name: String)) => {
|
291 | path.push_str(&format!("/ {}" , name.trim_matches(|c| c == ' \'' )));
|
292 | }
|
293 | Segment::Selector(Selector::Index(index: i64)) => {
|
294 | path.push_str(&format!("/ {}" , index));
|
295 | }
|
296 | s: Segment => {
|
297 | return Err(JsonPathError::InvalidJsonPath(format!(
|
298 | "Invalid segment: {:?}" ,
|
299 | s
|
300 | )));
|
301 | }
|
302 | }
|
303 | }
|
304 | Ok(path)
|
305 | }
|
306 |
|
307 | #[cfg (test)]
|
308 | mod 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 | }
|
450 | |