smooth_json/
lib.rs

1#![allow(unknown_lints)]
2#![deny(missing_docs)]
3#![deny(rustdoc::missing_doc_code_examples)]
4
5//! smooth-json
6//!
7//! `smooth-json` provides a utility to flatten a `serde_json` `Value` into a flat `serde_json` `Object`
8//! # Examples
9//! ```
10//! use smooth_json::Flattener;
11//! ```
12
13use serde_json::json;
14use serde_json::Map;
15use serde_json::Value;
16
17/// Flattener is the main driver when flattening JSON
18/// # Examples
19/// ```
20/// use smooth_json;
21///
22/// let flattener = smooth_json::Flattener { ..Default::default() };
23/// ```
24pub struct Flattener<'a> {
25    /// Alternate separator used between keys when flattening
26    /// # Examples
27    /// ```
28    /// use smooth_json;
29    /// let flattener = smooth_json::Flattener { separator: "_", ..Default::default()};
30    /// ```
31    pub separator: &'a str,
32    /// Opinionated flattening format that places values in an array if the object is nested inside an array
33    /// # Examples
34    /// ```
35    /// use smooth_json;
36    /// let flattener = smooth_json::Flattener { alt_array_flattening: true, ..Default::default()};
37    /// ```
38    pub alt_array_flattening: bool,
39    /// Completely flatten JSON and keep array structure in the key when flattening
40    /// # Examples
41    /// ```
42    /// use smooth_json;
43    /// let flattener = smooth_json::Flattener { preserve_arrays: true, ..Default::default()};
44    /// ```
45    pub preserve_arrays: bool,
46}
47
48impl<'a> Default for Flattener<'a> {
49    fn default() -> Self {
50        Flattener {
51            separator: ".",
52            alt_array_flattening: false,
53            preserve_arrays: false,
54        }
55    }
56}
57
58/// This implementation defines the core usage for the `Flattener` structure.
59/// # Examples
60/// ```
61/// use smooth_json;
62/// use serde_json::json;
63///
64/// let flattener = smooth_json::Flattener::new();
65/// let example = json!({
66///     "a": {
67///         "b": "c"
68///     }
69///  });
70///
71/// let flattened_example = flattener.flatten(&example);
72/// ```
73impl<'a> Flattener<'a> {
74    /// Returns a flattener with the default arguments
75    /// # Examples
76    /// ```
77    /// use smooth_json;
78    ///
79    /// let flattener = smooth_json::Flattener::new();
80    /// ```
81    pub fn new() -> Self {
82        Flattener {
83            ..Default::default()
84        }
85    }
86
87    /// Flattens JSON variants into a JSON object
88    ///
89    /// # Arguments
90    ///
91    /// * `json` - A serde_json Value to flatten
92    ///
93    /// # Examples
94    /// ```
95    /// use smooth_json;
96    /// use serde_json::json;
97    ///
98    /// let flattener = smooth_json::Flattener::new();
99    /// let example = json!({
100    ///     "name": "John Doe",
101    ///     "age": 43,
102    ///     "address": {
103    ///         "street": "10 Downing Street",
104    ///         "city": "London"
105    ///     },
106    ///     "phones": [
107    ///         "+44 1234567",
108    ///         "+44 2345678"
109    ///     ]
110    ///  });
111    ///
112    /// let flattened_example = flattener.flatten(&example);
113    /// ```
114    pub fn flatten(&self, json: &Value) -> Value {
115        let mut flattened_val = Map::<String, Value>::new();
116        match json {
117            Value::Array(obj_arr) => {
118                self.flatten_array(&mut flattened_val, &"".to_string(), obj_arr)
119            }
120            Value::Object(obj_val) => self.flatten_object(&mut flattened_val, None, obj_val, false),
121            _ => self.flatten_value(&mut flattened_val, &"".to_string(), json, false),
122        }
123        Value::Object(flattened_val)
124    }
125
126    fn flatten_object(
127        &self,
128        builder: &mut Map<String, Value>,
129        identifier: Option<&String>,
130        obj: &Map<String, Value>,
131        arr: bool,
132    ) {
133        for (k, v) in obj {
134            let expanded_identifier = identifier.map_or_else(
135                || k.clone(),
136                |identifier| format!("{identifier}{}{k}", self.separator),
137            );
138
139            match v {
140                Value::Object(obj_val) => {
141                    self.flatten_object(builder, Some(&expanded_identifier), obj_val, arr)
142                }
143                Value::Array(obj_arr) => self.flatten_array(builder, &expanded_identifier, obj_arr),
144                _ => self.flatten_value(builder, &expanded_identifier, v, arr),
145            }
146        }
147    }
148
149    fn flatten_array(
150        &self,
151        builder: &mut Map<String, Value>,
152        identifier: &String,
153        obj: &Vec<Value>,
154    ) {
155        for (k, v) in obj.iter().enumerate() {
156            let with_key = format!("{identifier}{}{k}", self.separator);
157            match v {
158                Value::Object(obj_val) => self.flatten_object(
159                    builder,
160                    Some(if self.preserve_arrays {
161                        &with_key
162                    } else {
163                        identifier
164                    }),
165                    obj_val,
166                    self.alt_array_flattening,
167                ),
168                Value::Array(obj_arr) => self.flatten_array(
169                    builder,
170                    if self.preserve_arrays {
171                        &with_key
172                    } else {
173                        identifier
174                    },
175                    obj_arr,
176                ),
177                _ => self.flatten_value(
178                    builder,
179                    if self.preserve_arrays {
180                        &with_key
181                    } else {
182                        identifier
183                    },
184                    v,
185                    self.alt_array_flattening,
186                ),
187            }
188        }
189    }
190
191    fn flatten_value(
192        &self,
193        builder: &mut Map<String, Value>,
194        identifier: &String,
195        obj: &Value,
196        arr: bool,
197    ) {
198        if let Some(v) = builder.get_mut(identifier) {
199            if let Some(arr) = v.as_array_mut() {
200                arr.push(obj.clone());
201            } else {
202                let new_val = json!(vec![v, obj]);
203                builder.remove(identifier);
204                builder.insert(identifier.to_string(), new_val);
205            }
206        } else {
207            builder.insert(
208                identifier.to_string(),
209                if arr {
210                    json!(vec![obj.clone()])
211                } else {
212                    obj.clone()
213                },
214            );
215        }
216    }
217}
218
219#[cfg(test)]
220mod tests {
221    use super::*;
222
223    use serde_json::json;
224
225    #[test]
226    fn serde_example() {
227        let flattener = Flattener::new();
228        let base: Value = json!({
229            "name": "John Doe",
230            "age": 43,
231            "address": {
232                "street": "10 Downing Street",
233                "city": "London"
234            },
235            "phones": [
236                "+44 1234567",
237                "+44 2345678"
238            ]
239        });
240
241        let flat = flattener.flatten(&base);
242
243        assert_eq!(
244            flat,
245            json!({
246                "name": "John Doe",
247                "age": 43,
248                "address.street": "10 Downing Street",
249                "address.city": "London",
250                "phones": [
251                    "+44 1234567",
252                    "+44 2345678"
253                ]
254            })
255        );
256    }
257
258    #[test]
259    fn collision_object() {
260        let flattener = Flattener::new();
261        let base: Value = json!({
262          "a": {
263            "b": "c",
264          },
265          "a.b": "d",
266        });
267        let flat = flattener.flatten(&base);
268
269        assert_eq!(
270            flat,
271            json!({
272                "a.b": ["c", "d"],
273            })
274        );
275    }
276
277    #[test]
278    fn collision_array() {
279        let flattener = Flattener::new();
280        let flattener_alt = Flattener {
281            alt_array_flattening: true,
282            ..Default::default()
283        };
284
285        let base: Value = json!({
286          "a": [
287            { "b": "c" },
288            { "b": "d", "c": "e" },
289            [35],
290          ],
291          "a.b": "f",
292        });
293
294        let flat = flattener.flatten(&base);
295        let flat_alt = flattener_alt.flatten(&base);
296
297        assert_eq!(
298            flat,
299            json!({
300                "a.b": ["c", "d", "f"],
301                "a.c": "e",
302                "a": 35,
303            })
304        );
305
306        assert_eq!(
307            flat_alt,
308            json!({
309                "a.b": ["c", "d", "f"],
310                "a.c": ["e"],
311                "a": [35],
312            })
313        );
314    }
315
316    #[test]
317    fn nested_arrays() {
318        let flattener = Flattener::new();
319        let flattener_alt = Flattener {
320            alt_array_flattening: true,
321            ..Default::default()
322        };
323
324        let base: Value = json!({
325          "a": [
326            ["b", "c"],
327            { "d": "e" },
328            ["f", "g"],
329            [
330                { "h": "i" },
331                { "d": "j" },
332            ],
333            ["k", "l"],
334          ]
335        });
336        let flat = flattener.flatten(&base);
337        let flat_alt = flattener_alt.flatten(&base);
338
339        assert_eq!(
340            flat,
341            json!({
342                "a": ["b", "c", "f", "g", "k", "l"],
343                "a.d": ["e", "j"],
344                "a.h": "i",
345            })
346        );
347
348        assert_eq!(
349            flat_alt,
350            json!({
351                "a": ["b", "c", "f", "g", "k", "l"],
352                "a.d": ["e", "j"],
353                "a.h": ["i"],
354            })
355        );
356    }
357
358    #[test]
359    fn nested_arrays_and_objects() {
360        let flattener = Flattener::new();
361        let flattener_alt = Flattener {
362            alt_array_flattening: true,
363            ..Default::default()
364        };
365
366        let base: Value = json!({
367          "a": [
368            "b",
369            ["c", "d"],
370            { "e": ["f", "g"] },
371            [
372                { "h": "i" },
373                { "e": ["j", { "z": "y" }] },
374            ],
375            ["l"],
376            "m",
377          ]
378        });
379        let flat = flattener.flatten(&base);
380        let flat_alt = flattener_alt.flatten(&base);
381
382        assert_eq!(
383            flat,
384            json!({
385                "a": ["b", "c", "d", "l", "m"],
386                "a.e": ["f", "g", "j"],
387                "a.h": "i",
388                "a.e.z": "y",
389            })
390        );
391
392        assert_eq!(
393            flat_alt,
394            json!({
395                "a": ["b", "c", "d", "l", "m"],
396                "a.e": ["f", "g", "j"],
397                "a.h": ["i"],
398                "a.e.z": ["y"],
399            })
400        )
401    }
402
403    #[test]
404    fn custom_separator() {
405        let flattener = Flattener {
406            separator: "$",
407            ..Default::default()
408        };
409
410        let input: Value = json!({
411        "a": {
412            "b": 1
413        }});
414
415        let result: Value = flattener.flatten(&input);
416        assert_eq!(
417            result,
418            json!({
419                "a$b": 1
420            })
421        );
422    }
423    #[test]
424    fn object() {
425        let flattener = Flattener::new();
426
427        let input: Value = json!({
428            "a": {
429                "b": "1",
430                "c": "2",
431                "d": "3"
432            }
433        });
434
435        let result: Value = flattener.flatten(&input);
436        assert_eq!(
437            result,
438            json!({
439                "a.b": "1",
440                "a.c": "2",
441                "a.d": "3"
442            })
443        );
444    }
445
446    #[test]
447    fn array() {
448        let flattener = Flattener::new();
449
450        let input: Value = json!({
451            "a": [
452                {"b": "1"},
453                {"b": "2"},
454                {"b": "3"},
455            ]
456        });
457
458        let result: Value = flattener.flatten(&input);
459        assert_eq!(
460            result,
461            json!({
462                "a.b": ["1", "2", "3"]
463            })
464        );
465    }
466
467    #[test]
468    fn array_preserve() {
469        let flattener = Flattener {
470            preserve_arrays: true,
471            ..Default::default()
472        };
473
474        let input: Value = json!({
475            "a": [
476                {"b": "1"},
477                {"b": "2"},
478                {"b": "3"},
479            ]
480        });
481
482        let result: Value = flattener.flatten(&input);
483        assert_eq!(
484            result,
485            json!({
486                "a.0.b": "1",
487                "a.1.b": "2",
488                "a.2.b": "3"
489            })
490        );
491    }
492
493    #[test]
494    fn array_no_collision() {
495        let flattener = Flattener::new();
496        let flattener_alt = Flattener {
497            alt_array_flattening: true,
498            ..Default::default()
499        };
500
501        let input: Value = json!({
502            "a": [
503                {"b": ["1"]}
504            ]
505        });
506
507        let flat: Value = flattener.flatten(&input);
508        let flat_alt = flattener_alt.flatten(&input);
509
510        assert_eq!(
511            flat,
512            json!({
513                "a.b": "1"
514            })
515        );
516
517        assert_eq!(
518            flat_alt,
519            json!({
520                "a.b": ["1"]
521            })
522        );
523    }
524
525    // its allowed https://ecma-international.org/publications-and-standards/standards/ecma-404/
526    #[test]
527    fn arr_no_key() {
528        let flattener = Flattener::new();
529
530        let input: Value = json!(["a", "b"]);
531
532        let result: Value = flattener.flatten(&input);
533
534        assert_eq!(result, json!({"": ["a", "b"]}));
535    }
536
537    // its allowed https://ecma-international.org/publications-and-standards/standards/ecma-404/
538    #[test]
539    fn arr_empty_key() {
540        let flattener = Flattener::new();
541
542        let input: Value = json!({
543            "": [
544                "a",
545                "b",
546                {"b": ["1"]}
547            ],
548        });
549        let result: Value = flattener.flatten(&input);
550
551        assert_eq!(result, json!({"": ["a", "b"], ".b": "1"}));
552    }
553
554    #[test]
555    fn only_value() {
556        let flattener = Flattener::new();
557
558        let input: Value = json!("abc");
559        let result: Value = flattener.flatten(&input);
560
561        assert_eq!(result, json!({"": "abc"}));
562    }
563
564    #[test]
565    fn nested_array_preserve() {
566        let flattener = Flattener {
567            preserve_arrays: true,
568            ..Default::default()
569        };
570
571        let input: Value = json!({
572        "a": [
573                    "b",
574                    ["c", "d"],
575                    { "e": ["f", "g"] },
576                    [
577                        { "h": "i" },
578                        { "e": ["j", { "z": "y" }] }
579                    ],
580                    ["l"],
581                    "m"
582                 ]
583        });
584
585        let result: Value = flattener.flatten(&input);
586
587        assert_eq!(
588            result,
589            json!({
590              "a.0": "b",
591              "a.1.0": "c",
592              "a.1.1": "d",
593              "a.2.e.0": "f",
594              "a.2.e.1": "g",
595              "a.3.0.h": "i",
596              "a.3.1.e.0": "j",
597              "a.3.1.e.1.z": "y",
598              "a.4.0": "l",
599              "a.5": "m"
600            })
601        )
602    }
603}