permissive_json_pointer/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use std::collections::HashSet;
4
5use serde_json::*;
6
7type Document = Map<String, Value>;
8
9const SPLIT_SYMBOL: char = '.';
10
11/// Returns `true` if the `subset` is contained in the `main` string.
12fn contained_in(selector: &str, key: &str) -> bool {
13    selector.starts_with(key)
14        && selector[key.len()..]
15            .chars()
16            .next()
17            .map(|c| c == SPLIT_SYMBOL)
18            .unwrap_or(true)
19}
20
21/// Map the selected leaf values of a json allowing you to update only the fields that were selected.
22/// ```
23/// use serde_json::{Value, json};
24/// use permissive_json_pointer::map_leaf_values;
25///
26/// let mut value: Value = json!({
27///     "jean": {
28///         "age": 8,
29///         "race": {
30///             "name": "bernese mountain",
31///             "size": "80cm",
32///         }
33///     }
34/// });
35/// map_leaf_values(
36///     value.as_object_mut().unwrap(),
37///     ["jean.race.name"],
38///     |key, value| match (value, dbg!(key)) {
39///         (Value::String(name), "jean.race.name") => *name = "patou".to_string(),
40///         _ => unreachable!(),
41///     },
42/// );
43/// assert_eq!(
44///     value,
45///     json!({
46///         "jean": {
47///             "age": 8,
48///             "race": {
49///                 "name": "patou",
50///                 "size": "80cm",
51///             }
52///         }
53///     })
54/// );
55/// ```
56pub fn map_leaf_values<'a>(
57    value: &mut Map<String, Value>,
58    selectors: impl IntoIterator<Item = &'a str>,
59    mut mapper: impl FnMut(&str, &mut Value),
60) {
61    let selectors: Vec<_> = selectors.into_iter().collect();
62    map_leaf_values_in_object(value, &selectors, "", &mut mapper);
63}
64
65pub fn map_leaf_values_in_object<'a>(
66    value: &mut Map<String, Value>,
67    selectors: &[&'a str],
68    base_key: &str,
69    mapper: &mut impl FnMut(&str, &mut Value),
70) {
71    for (key, value) in value.iter_mut() {
72        let base_key = if base_key.is_empty() {
73            key.to_string()
74        } else {
75            format!("{}{}{}", base_key, SPLIT_SYMBOL, key)
76        };
77
78        // here if the user only specified `doggo` we need to iterate in all the fields of `doggo`
79        // so we check the contained_in on both side
80        let should_continue = selectors.into_iter().any(|ref selector| {
81            contained_in(selector, &base_key) || contained_in(&base_key, selector)
82        });
83
84        if should_continue {
85            match value {
86                Value::Object(object) => {
87                    map_leaf_values_in_object(object, selectors, &base_key, mapper)
88                }
89                Value::Array(array) => {
90                    map_leaf_values_in_array(array, selectors, &base_key, mapper)
91                }
92                value => mapper(&base_key, value),
93            }
94        }
95    }
96}
97
98pub fn map_leaf_values_in_array(
99    values: &mut Vec<Value>,
100    selectors: &[&str],
101    base_key: &str,
102    mapper: &mut impl FnMut(&str, &mut Value),
103) {
104    for value in values.iter_mut() {
105        match value {
106            Value::Object(object) => {
107                map_leaf_values_in_object(object, selectors, &base_key, mapper)
108            }
109            Value::Array(array) => map_leaf_values_in_array(array, selectors, &base_key, mapper),
110            value => mapper(&base_key, value),
111        }
112    }
113}
114
115/// Permissively selects values in a json with a list of selectors.
116/// Returns a new json containing all the selected fields.
117/// ```
118/// use serde_json::*;
119/// use permissive_json_pointer::select_values;
120///
121/// let value: Value = json!({
122///     "name": "peanut",
123///     "age": 8,
124///     "race": {
125///         "name": "bernese mountain",
126///         "avg_age": 12,
127///         "size": "80cm",
128///     },
129/// });
130/// let value: &Map<String, Value> = value.as_object().unwrap();
131///
132/// let res: Value = select_values(value, vec!["name", "race.name"]).into();
133/// assert_eq!(
134///     res,
135///     json!({
136///         "name": "peanut",
137///         "race": {
138///             "name": "bernese mountain",
139///         },
140///     })
141/// );
142/// ```
143pub fn select_values<'a>(
144    value: &Map<String, Value>,
145    selectors: impl IntoIterator<Item = &'a str>,
146) -> Map<String, Value> {
147    let selectors = selectors.into_iter().collect();
148    create_value(value, selectors)
149}
150
151fn create_value(value: &Document, mut selectors: HashSet<&str>) -> Document {
152    let mut new_value: Document = Map::new();
153
154    for (key, value) in value.iter() {
155        // first we insert all the key at the root level
156        if selectors.contains(key as &str) {
157            new_value.insert(key.to_string(), value.clone());
158            // if the key was simple we can delete it and move to
159            // the next key
160            if is_simple(key) {
161                selectors.remove(key as &str);
162                continue;
163            }
164        }
165
166        // we extract all the sub selectors matching the current field
167        // if there was [person.name, person.age] and if we are on the field
168        // `person`. Then we generate the following sub selectors: [name, age].
169        let sub_selectors: HashSet<&str> = selectors
170            .iter()
171            .filter(|s| contained_in(s, key))
172            .filter_map(|s| s.trim_start_matches(key).get(SPLIT_SYMBOL.len_utf8()..))
173            .collect();
174
175        if !sub_selectors.is_empty() {
176            match value {
177                Value::Array(array) => {
178                    let array = create_array(array, &sub_selectors);
179                    if !array.is_empty() {
180                        new_value.insert(key.to_string(), array.into());
181                    }
182                }
183                Value::Object(object) => {
184                    let object = create_value(object, sub_selectors);
185                    if !object.is_empty() {
186                        new_value.insert(key.to_string(), object.into());
187                    }
188                }
189                _ => (),
190            }
191        }
192    }
193
194    new_value
195}
196
197fn create_array(array: &Vec<Value>, selectors: &HashSet<&str>) -> Vec<Value> {
198    let mut res = Vec::new();
199
200    for value in array {
201        match value {
202            Value::Array(array) => {
203                let array = create_array(array, selectors);
204                if !array.is_empty() {
205                    res.push(array.into());
206                }
207            }
208            Value::Object(object) => {
209                let object = create_value(object, selectors.clone());
210                if !object.is_empty() {
211                    res.push(object.into());
212                }
213            }
214            _ => (),
215        }
216    }
217
218    res
219}
220
221fn is_simple(key: impl AsRef<str>) -> bool {
222    !key.as_ref().contains(SPLIT_SYMBOL)
223}
224
225#[cfg(test)]
226mod tests {
227    use big_s::S;
228
229    use super::*;
230
231    #[test]
232    fn test_contained_in() {
233        assert!(contained_in("animaux", "animaux"));
234        assert!(contained_in("animaux.chien", "animaux"));
235        assert!(contained_in(
236            "animaux.chien.race.bouvier bernois.fourrure.couleur",
237            "animaux"
238        ));
239        assert!(contained_in(
240            "animaux.chien.race.bouvier bernois.fourrure.couleur",
241            "animaux.chien"
242        ));
243        assert!(contained_in(
244            "animaux.chien.race.bouvier bernois.fourrure.couleur",
245            "animaux.chien.race.bouvier bernois"
246        ));
247        assert!(contained_in(
248            "animaux.chien.race.bouvier bernois.fourrure.couleur",
249            "animaux.chien.race.bouvier bernois.fourrure"
250        ));
251        assert!(contained_in(
252            "animaux.chien.race.bouvier bernois.fourrure.couleur",
253            "animaux.chien.race.bouvier bernois.fourrure.couleur"
254        ));
255
256        // -- the wrongs
257        assert!(!contained_in("chien", "chat"));
258        assert!(!contained_in("animaux", "animaux.chien"));
259        assert!(!contained_in("animaux.chien", "animaux.chat"));
260
261        // -- the strange edge cases
262        assert!(!contained_in("animaux.chien", "anima"));
263        assert!(!contained_in("animaux.chien", "animau"));
264        assert!(!contained_in("animaux.chien", "animaux."));
265        assert!(!contained_in("animaux.chien", "animaux.c"));
266        assert!(!contained_in("animaux.chien", "animaux.ch"));
267        assert!(!contained_in("animaux.chien", "animaux.chi"));
268        assert!(!contained_in("animaux.chien", "animaux.chie"));
269    }
270
271    #[test]
272    fn simple_key() {
273        let value: Value = json!({
274            "name": "peanut",
275            "age": 8,
276            "race": {
277                "name": "bernese mountain",
278                "avg_age": 12,
279                "size": "80cm",
280            }
281        });
282        let value: &Document = value.as_object().unwrap();
283
284        let res: Value = select_values(value, vec!["name"]).into();
285        assert_eq!(
286            res,
287            json!({
288                "name": "peanut",
289            })
290        );
291
292        let res: Value = select_values(value, vec!["age"]).into();
293        assert_eq!(
294            res,
295            json!({
296                "age": 8,
297            })
298        );
299
300        let res: Value = select_values(value, vec!["name", "age"]).into();
301        assert_eq!(
302            res,
303            json!({
304                "name": "peanut",
305                "age": 8,
306            })
307        );
308
309        let res: Value = select_values(value, vec!["race"]).into();
310        assert_eq!(
311            res,
312            json!({
313                "race": {
314                    "name": "bernese mountain",
315                    "avg_age": 12,
316                    "size": "80cm",
317                }
318            })
319        );
320
321        let res: Value = select_values(value, vec!["name", "age", "race"]).into();
322        assert_eq!(
323            res,
324            json!({
325                "name": "peanut",
326                "age": 8,
327                "race": {
328                    "name": "bernese mountain",
329                    "avg_age": 12,
330                    "size": "80cm",
331                }
332            })
333        );
334    }
335
336    #[test]
337    fn complex_key() {
338        let value: Value = json!({
339            "name": "peanut",
340            "age": 8,
341            "race": {
342                "name": "bernese mountain",
343                "avg_age": 12,
344                "size": "80cm",
345            }
346        });
347        let value: &Document = value.as_object().unwrap();
348
349        let res: Value = select_values(value, vec!["race"]).into();
350        assert_eq!(
351            res,
352            json!({
353                "race": {
354                    "name": "bernese mountain",
355                    "avg_age": 12,
356                    "size": "80cm",
357                }
358            })
359        );
360
361        println!("RIGHT BEFORE");
362
363        let res: Value = select_values(value, vec!["race.name"]).into();
364        assert_eq!(
365            res,
366            json!({
367                "race": {
368                    "name": "bernese mountain",
369                }
370            })
371        );
372
373        let res: Value = select_values(value, vec!["race.name", "race.size"]).into();
374        assert_eq!(
375            res,
376            json!({
377                "race": {
378                    "name": "bernese mountain",
379                    "size": "80cm",
380                }
381            })
382        );
383
384        let res: Value = select_values(
385            value,
386            vec!["race.name", "race.size", "race.avg_age", "race.size", "age"],
387        )
388        .into();
389        assert_eq!(
390            res,
391            json!({
392                "age": 8,
393                "race": {
394                    "name": "bernese mountain",
395                    "avg_age": 12,
396                    "size": "80cm",
397                }
398            })
399        );
400
401        let res: Value = select_values(value, vec!["race.name", "race"]).into();
402        assert_eq!(
403            res,
404            json!({
405                "race": {
406                    "name": "bernese mountain",
407                    "avg_age": 12,
408                    "size": "80cm",
409                }
410            })
411        );
412
413        let res: Value = select_values(value, vec!["race", "race.name"]).into();
414        assert_eq!(
415            res,
416            json!({
417                "race": {
418                    "name": "bernese mountain",
419                    "avg_age": 12,
420                    "size": "80cm",
421                }
422            })
423        );
424    }
425
426    #[test]
427    fn multi_level_nested() {
428        let value: Value = json!({
429            "jean": {
430                "age": 8,
431                "race": {
432                    "name": "bernese mountain",
433                    "size": "80cm",
434                }
435            }
436        });
437        let value: &Document = value.as_object().unwrap();
438
439        let res: Value = select_values(value, vec!["jean"]).into();
440        assert_eq!(
441            res,
442            json!({
443                "jean": {
444                    "age": 8,
445                    "race": {
446                        "name": "bernese mountain",
447                        "size": "80cm",
448                    }
449                }
450            })
451        );
452
453        let res: Value = select_values(value, vec!["jean.age"]).into();
454        assert_eq!(
455            res,
456            json!({
457                "jean": {
458                    "age": 8,
459                }
460            })
461        );
462
463        let res: Value = select_values(value, vec!["jean.race.size"]).into();
464        assert_eq!(
465            res,
466            json!({
467                "jean": {
468                    "race": {
469                        "size": "80cm",
470                    }
471                }
472            })
473        );
474
475        let res: Value = select_values(value, vec!["jean.race.name", "jean.age"]).into();
476        assert_eq!(
477            res,
478            json!({
479                "jean": {
480                    "age": 8,
481                    "race": {
482                        "name": "bernese mountain",
483                    }
484                }
485            })
486        );
487
488        let res: Value = select_values(value, vec!["jean.race"]).into();
489        assert_eq!(
490            res,
491            json!({
492                "jean": {
493                    "race": {
494                        "name": "bernese mountain",
495                        "size": "80cm",
496                    }
497                }
498            })
499        );
500    }
501
502    #[test]
503    fn array_and_deep_nested() {
504        let value: Value = json!({
505            "doggos": [
506                {
507                    "jean": {
508                        "age": 8,
509                        "race": {
510                            "name": "bernese mountain",
511                            "size": "80cm",
512                        }
513                    }
514                },
515                {
516                    "marc": {
517                        "age": 4,
518                        "race": {
519                            "name": "golden retriever",
520                            "size": "60cm",
521                        }
522                    }
523                },
524            ]
525        });
526        let value: &Document = value.as_object().unwrap();
527
528        let res: Value = select_values(value, vec!["doggos.jean"]).into();
529        assert_eq!(
530            res,
531            json!({
532                "doggos": [
533                    {
534                        "jean": {
535                            "age": 8,
536                            "race": {
537                                "name": "bernese mountain",
538                                "size": "80cm",
539                            }
540                        }
541                    }
542                ]
543            })
544        );
545
546        let res: Value = select_values(value, vec!["doggos.marc"]).into();
547        assert_eq!(
548            res,
549            json!({
550                "doggos": [
551                    {
552                        "marc": {
553                            "age": 4,
554                            "race": {
555                                "name": "golden retriever",
556                                "size": "60cm",
557                            }
558                        }
559                    }
560                ]
561            })
562        );
563
564        let res: Value = select_values(value, vec!["doggos.marc.race"]).into();
565        assert_eq!(
566            res,
567            json!({
568                "doggos": [
569                    {
570                        "marc": {
571                            "race": {
572                                "name": "golden retriever",
573                                "size": "60cm",
574                            }
575                        }
576                    }
577                ]
578            })
579        );
580
581        let res: Value =
582            select_values(value, vec!["doggos.marc.race.name", "doggos.marc.age"]).into();
583
584        assert_eq!(
585            res,
586            json!({
587                "doggos": [
588                    {
589                        "marc": {
590                            "age": 4,
591                            "race": {
592                                "name": "golden retriever",
593                            }
594                        }
595                    }
596                ]
597            })
598        );
599
600        let res: Value = select_values(
601            value,
602            vec![
603                "doggos.marc.race.name",
604                "doggos.marc.age",
605                "doggos.jean.race.name",
606                "other.field",
607            ],
608        )
609        .into();
610
611        assert_eq!(
612            res,
613            json!({
614                "doggos": [
615                    {
616                        "jean": {
617                            "race": {
618                                "name": "bernese mountain",
619                            }
620                        }
621                    },
622                    {
623                        "marc": {
624                            "age": 4,
625                            "race": {
626                                "name": "golden retriever",
627                            }
628                        }
629                    }
630                ]
631            })
632        );
633    }
634
635    #[test]
636    fn all_conflict_variation() {
637        let value: Value = json!({
638           "pet.dog.name": "jean",
639           "pet.dog": {
640             "name": "bob"
641           },
642           "pet": {
643             "dog.name": "michel"
644           },
645           "pet": {
646             "dog": {
647               "name": "milan"
648             }
649           }
650        });
651        let value: &Document = value.as_object().unwrap();
652
653        let res: Value = select_values(value, vec!["pet.dog.name"]).into();
654        assert_eq!(
655            res,
656            json!({
657               "pet.dog.name": "jean",
658               "pet.dog": {
659                 "name": "bob"
660               },
661               "pet": {
662                 "dog.name": "michel"
663               },
664               "pet": {
665                 "dog": {
666                   "name": "milan"
667                 }
668               }
669            })
670        );
671
672        let value: Value = json!({
673           "pet.dog.name": "jean",
674           "pet.dog": {
675             "name": "bob",
676           },
677           "pet": {
678             "dog.name": "michel",
679             "dog": {
680               "name": "milan",
681             }
682           }
683        });
684        let value: &Document = value.as_object().unwrap();
685
686        let res: Value = select_values(value, vec!["pet.dog.name", "pet.dog", "pet"]).into();
687
688        assert_eq!(
689            res,
690            json!({
691               "pet.dog.name": "jean",
692               "pet.dog": {
693                 "name": "bob",
694               },
695               "pet": {
696                 "dog.name": "michel",
697                 "dog": {
698                   "name": "milan",
699                 }
700               }
701            })
702        );
703    }
704
705    #[test]
706    fn map_object() {
707        let mut value: Value = json!({
708            "jean": {
709                "age": 8,
710                "race": {
711                    "name": "bernese mountain",
712                    "size": "80cm",
713                }
714            }
715        });
716
717        map_leaf_values(
718            value.as_object_mut().unwrap(),
719            ["jean.race.name"],
720            |key, value| match (value, dbg!(key)) {
721                (Value::String(name), "jean.race.name") => *name = S("patou"),
722                _ => unreachable!(),
723            },
724        );
725
726        assert_eq!(
727            value,
728            json!({
729                "jean": {
730                    "age": 8,
731                    "race": {
732                        "name": "patou",
733                        "size": "80cm",
734                    }
735                }
736            })
737        );
738
739        let mut value: Value = json!({
740            "jean": {
741                "age": 8,
742                "race": {
743                    "name": "bernese mountain",
744                    "size": "80cm",
745                }
746            },
747            "bob": "lolpied",
748        });
749
750        let mut calls = 0;
751        map_leaf_values(value.as_object_mut().unwrap(), ["jean"], |key, value| {
752            calls += 1;
753            match (value, key) {
754                (Value::String(name), "jean.race.name") => *name = S("patou"),
755                _ => println!("Called with {key}"),
756            }
757        });
758
759        assert_eq!(calls, 3);
760        assert_eq!(
761            value,
762            json!({
763                "jean": {
764                    "age": 8,
765                    "race": {
766                        "name": "patou",
767                        "size": "80cm",
768                    }
769                },
770                "bob": "lolpied",
771            })
772        );
773    }
774}