flatten_json_object/
lib.rs

1//! Robust Rust library for flattening JSON objects
2//!
3//! Given a JSON object it produces another one with all the nested objects and arrays flattened.
4//! The string used to separate the concatenated keys, and the way the arrays are
5//! formatted can be configured.
6//!
7//! ### Notes
8//! - Empty arrays and objects are ignored by default, but it's configurable.
9//! - The empty key `""` and the JSON `null` value can be used without problems and are preserved.
10//! - Having two or more keys that end being the same after flattening the object returns an error.
11//! - The JSON value passed to be flattened must be an object. The object can contain any valid JSON,
12//!   though.
13//!
14//! ### Usage example
15//!```rust
16//!# use std::error::Error;
17//!#
18//!# fn main() -> Result<(), Box<dyn Error>> {
19//!#
20//! use flatten_json_object::ArrayFormatting;
21//! use flatten_json_object::Flattener;
22//! use serde_json::json;
23//!
24//! let obj = json!({
25//!     "a": {
26//!         "b": [1, 2.0, "c", null, true, {}, []],
27//!         "" : "my_key_is_empty"
28//!     },
29//!     "" : "my_key_is_also_empty"
30//! });
31//! assert_eq!(
32//!     Flattener::new()
33//!         .set_key_separator(".")
34//!         .set_array_formatting(ArrayFormatting::Surrounded {
35//!             start: "[".to_string(),
36//!             end: "]".to_string()
37//!         })
38//!         .set_preserve_empty_arrays(false)
39//!         .set_preserve_empty_objects(false)
40//!         .flatten(&obj)?,
41//!     json!({
42//!         "a.b[0]": 1,
43//!         "a.b[1]": 2.0,
44//!         "a.b[2]": "c",
45//!         "a.b[3]": null,
46//!         "a.b[4]": true,
47//!         "a.": "my_key_is_empty",
48//!         "": "my_key_is_also_empty",
49//!     })
50//! );
51//!#
52//!#     Ok(())
53//!# }
54//! ```
55
56use serde_json::value::Map;
57use serde_json::value::Value;
58
59pub use error::Error;
60
61mod error;
62
63/// Enum to specify how arrays are formatted.
64#[derive(Clone, Debug, Eq, PartialEq)]
65pub enum ArrayFormatting {
66    /// Uses only the key separator. Example:  `{"a": ["b"]}` => `{"a.0": "b"}`
67    Plain,
68
69    /// Does not use the key separator. Instead, the position in the array is surrounded with the
70    /// provided `start` and `end` strings.
71    /// Example: If `start` is `[` and `end` is `]` then `{"a": ["b"]}` => `{"a[0]": "b"}`
72    Surrounded { start: String, end: String },
73}
74
75/// Basic struct of this crate. It contains the configuration. Instantiate it and use the method
76/// `flatten` to flatten a JSON object.
77#[derive(Clone, Debug, Eq, PartialEq)]
78pub struct Flattener {
79    /// String used to separate the keys after the object it's flattened
80    key_separator: String,
81    /// Enum that indicates how arrays are formatted
82    array_formatting: ArrayFormatting,
83    /// If `true` all `[]` are preserved
84    preserve_empty_arrays: bool,
85    /// If `true` all `{}` are preserved
86    preserve_empty_objects: bool,
87}
88
89impl Default for Flattener {
90    fn default() -> Self {
91        Self::new()
92    }
93}
94
95impl Flattener {
96    /// Creates a JSON object flattener with the default configuration.
97    #[must_use]
98    pub fn new() -> Self {
99        Flattener {
100            array_formatting: ArrayFormatting::Plain,
101            key_separator: ".".to_string(),
102            preserve_empty_arrays: false,
103            preserve_empty_objects: false,
104        }
105    }
106
107    /// Changes the string used to separate keys in the resulting flattened object.
108    #[must_use]
109    pub fn set_key_separator(mut self, key_separator: &str) -> Self {
110        self.key_separator = key_separator.to_string();
111        self
112    }
113
114    /// Changes the way arrays are formatted. By default the position in the array is treated as a
115    /// normal key, but with this function we can change this behaviour.
116    #[must_use]
117    pub fn set_array_formatting(mut self, array_formatting: ArrayFormatting) -> Self {
118        self.array_formatting = array_formatting;
119        self
120    }
121
122    /// Changes the behaviour regarding empty arrays `[]`
123    #[must_use]
124    pub fn set_preserve_empty_arrays(mut self, value: bool) -> Self {
125        self.preserve_empty_arrays = value;
126        self
127    }
128
129    /// Changes the behaviour regarding empty objects `{}`
130    #[must_use]
131    pub fn set_preserve_empty_objects(mut self, value: bool) -> Self {
132        self.preserve_empty_objects = value;
133        self
134    }
135
136    #[must_use]
137    pub fn key_separator(&self) -> &str {
138        &self.key_separator
139    }
140
141    #[must_use]
142    pub fn array_formatting(&self) -> &ArrayFormatting {
143        &self.array_formatting
144    }
145
146    #[must_use]
147    pub fn preserve_empty_arrays(&self) -> bool {
148        self.preserve_empty_arrays
149    }
150
151    #[must_use]
152    pub fn preserve_empty_objects(&self) -> bool {
153        self.preserve_empty_objects
154    }
155
156    /// Flattens the provided JSON object (`current`).
157    ///
158    /// It will return an error if flattening the object would make two keys to be the same,
159    /// overwriting a value. It will alre return an error if the JSON value passed it's not an object.
160    ///
161    /// # Errors
162    /// Will return `Err` if `to_flatten` it's not an object, or if flattening the object would
163    /// result in two or more keys colliding.
164    pub fn flatten(&self, to_flatten: &Value) -> Result<Value, error::Error> {
165        let mut flat = Map::<String, Value>::new();
166        self.flatten_value(to_flatten, "".to_owned(), 0, &mut flat)
167            .map(|_x| Value::Object(flat))
168    }
169
170    /// Flattens the passed JSON value (`current`), whose path is `parent_key` and its 0-based
171    /// depth is `depth`.  The result is stored in the JSON object `flattened`.
172    fn flatten_value(
173        &self,
174        current: &Value,
175        parent_key: String,
176        depth: u32,
177        flattened: &mut Map<String, Value>,
178    ) -> Result<(), error::Error> {
179        if depth == 0 {
180            match current {
181                Value::Object(map) => {
182                    if map.is_empty() {
183                        return Ok(()); // If the top level input object is empty there is nothing to do
184                    }
185                }
186                _ => return Err(error::Error::FirstLevelMustBeAnObject),
187            }
188        }
189
190        if let Some(current) = current.as_object() {
191            if current.is_empty() && self.preserve_empty_objects {
192                flattened.insert(parent_key, serde_json::json!({}));
193            } else {
194                self.flatten_object(current, &parent_key, depth, flattened)?;
195            }
196        } else if let Some(current) = current.as_array() {
197            if current.is_empty() && self.preserve_empty_arrays {
198                flattened.insert(parent_key, serde_json::json!([]));
199            } else {
200                self.flatten_array(current, &parent_key, depth, flattened)?;
201            }
202        } else {
203            if flattened.contains_key(&parent_key) {
204                return Err(error::Error::KeyWillBeOverwritten(parent_key));
205            }
206            flattened.insert(parent_key, current.clone());
207        }
208        Ok(())
209    }
210
211    /// Flattens the passed object (`current`), whose path is `parent_key` and its 0-based depth
212    /// is `depth`.  The result is stored in the JSON object `flattened`.
213    fn flatten_object(
214        &self,
215        current: &Map<String, Value>,
216        parent_key: &str,
217        depth: u32,
218        flattened: &mut Map<String, Value>,
219    ) -> Result<(), error::Error> {
220        for (k, v) in current.iter() {
221            let parent_key = if depth > 0 {
222                format!("{}{}{}", parent_key, self.key_separator, k)
223            } else {
224                k.to_string()
225            };
226            self.flatten_value(v, parent_key, depth + 1, flattened)?;
227        }
228        Ok(())
229    }
230
231    /// Flattens the passed array (`current`), whose path is `parent_key` and its 0-based depth
232    /// is `depth`.  The result is stored in the JSON object `flattened`.
233    fn flatten_array(
234        &self,
235        current: &[Value],
236        parent_key: &str,
237        depth: u32,
238        flattened: &mut Map<String, Value>,
239    ) -> Result<(), error::Error> {
240        for (i, obj) in current.iter().enumerate() {
241            let parent_key = match self.array_formatting {
242                ArrayFormatting::Plain => format!("{}{}{}", parent_key, self.key_separator, i),
243                ArrayFormatting::Surrounded { ref start, ref end } => {
244                    format!("{}{}{}{}", parent_key, start, i, end)
245                }
246            };
247            self.flatten_value(obj, parent_key, depth + 1, flattened)?;
248        }
249        Ok(())
250    }
251}
252
253#[cfg(test)]
254mod tests {
255    use super::ArrayFormatting;
256    use super::Flattener;
257    use crate::error::Error;
258    use rstest::rstest;
259    use serde_json::json;
260
261    #[rstest]
262    #[case("")]
263    #[case(".")]
264    #[case("-->")]
265    fn object_with_plain_values(#[case] key_separator: &str) {
266        let obj = json!({"int": 1, "float": 2.0, "str": "a", "bool": true, "null": null});
267        assert_eq!(
268            obj,
269            Flattener::new()
270                .set_key_separator(key_separator)
271                .flatten(&obj)
272                .unwrap()
273        );
274    }
275
276    /// Ensures that when using `ArrayFormatting::Plain` both arrays and objects are formatted
277    /// properly.
278    #[rstest]
279    #[case("")]
280    #[case(".")]
281    #[case("aaa")]
282    fn array_formatting_plain(#[case] key_separator: &str) {
283        let obj = json!({"s": {"a": [1, 2.0, "b", null, true]}});
284        assert_eq!(
285            Flattener::new()
286                .set_key_separator(key_separator)
287                .flatten(&obj)
288                .unwrap(),
289            json!({
290                format!("s{k}a{k}0", k = key_separator): 1,
291                format!("s{k}a{k}1", k = key_separator): 2.0,
292                format!("s{k}a{k}2", k = key_separator): "b",
293                format!("s{k}a{k}3", k = key_separator): null,
294                format!("s{k}a{k}4", k = key_separator): true,
295            })
296        );
297    }
298
299    /// Ensures that when using `ArrayFormatting::Surrounded` both the array start and end
300    /// decorations and the key separator work as expected.
301    #[rstest]
302    fn array_formatting_surrouded(
303        #[values("", ".", "-->")] key_separator: &str,
304        #[values("", "[", "{{")] array_fmt_start: &str,
305        #[values("", "]", "}}")] array_fmt_end: &str,
306    ) {
307        let obj = json!({"s": {"a": [1, 2.0, "b", null, true]}});
308        assert_eq!(
309            Flattener::new()
310                .set_key_separator(key_separator)
311                .set_array_formatting(ArrayFormatting::Surrounded {
312                    start: array_fmt_start.to_string(),
313                    end: array_fmt_end.to_string()
314                })
315                .flatten(&obj)
316                .unwrap(),
317            json!({
318                format!("s{}a{}0{}", key_separator, array_fmt_start, array_fmt_end): 1,
319                format!("s{}a{}1{}", key_separator, array_fmt_start, array_fmt_end): 2.0,
320                format!("s{}a{}2{}", key_separator, array_fmt_start, array_fmt_end): "b",
321                format!("s{}a{}3{}", key_separator, array_fmt_start, array_fmt_end): null,
322                format!("s{}a{}4{}", key_separator, array_fmt_start, array_fmt_end): true,
323            })
324        );
325    }
326
327    #[test]
328    fn nested_single_key_value() {
329        let obj = json!({"key": "value", "nested_key": {"key": "value"}});
330        assert_eq!(
331            Flattener::new().flatten(&obj).unwrap(),
332            json!({"key": "value", "nested_key.key": "value"}),
333        );
334    }
335
336    #[test]
337    fn nested_multiple_key_value() {
338        let obj = json!({"key": "value", "nested_key": {"key1": "value1", "key2": "value2"}});
339        assert_eq!(
340            Flattener::new().flatten(&obj).unwrap(),
341            json!({"key": "value", "nested_key.key1": "value1", "nested_key.key2": "value2"}),
342        );
343    }
344
345    #[test]
346    fn complex_nested_struct() {
347        let obj = json!({
348            "simple_key": "simple_value",
349            "key": [
350                "value1",
351                {"key": "value2"},
352                {"nested_array": [
353                    "nested1",
354                    "nested2",
355                    ["nested3", "nested4"]
356                ]}
357            ]
358        });
359        assert_eq!(
360            Flattener::new().flatten(&obj).unwrap(),
361            json!({"simple_key": "simple_value", "key.0": "value1", "key.1.key": "value2",
362                "key.2.nested_array.0": "nested1", "key.2.nested_array.1": "nested2",
363                "key.2.nested_array.2.0": "nested3", "key.2.nested_array.2.1": "nested4"}),
364        );
365    }
366
367    #[test]
368    fn overlapping_after_flattening_array() {
369        let obj = json!({"key": ["value1", "value2"], "key.0": "Oopsy"});
370        let res = Flattener::new().flatten(&obj);
371        assert!(res.is_err());
372        match res {
373            Err(Error::KeyWillBeOverwritten(key)) => assert_eq!(key, "key.0"),
374            Ok(_) => panic!("This should have failed"),
375            _ => panic!("Wrong kind of error"),
376        }
377    }
378
379    /// Ensure that empty arrays are not present in the result
380    #[test]
381    fn empty_array() {
382        let obj = json!({"key": []});
383        assert_eq!(Flattener::new().flatten(&obj).unwrap(), json!({}));
384    }
385
386    /// Ensure that empty objects are not present in the result
387    #[test]
388    fn empty_object() {
389        let obj = json!({"key": {}});
390        assert_eq!(Flattener::new().flatten(&obj).unwrap(), json!({}));
391    }
392
393    #[rstest]
394    fn empty_top_object(#[values(true, false)] preserve_empty_objects: bool) {
395        let obj = json!({});
396        assert_eq!(
397            Flattener::new()
398                .set_preserve_empty_objects(preserve_empty_objects)
399                .flatten(&obj)
400                .unwrap(),
401            json!({})
402        );
403    }
404
405    /// Ensure that if all the end values of the JSON object are either `[]` or `{}` the flattened
406    /// resulting object it's empty.
407    #[test]
408    fn empty_complex_object() {
409        let obj = json!({"key": {"key2": {}, "key3": [[], {}, {"k": {}, "q": []}]}});
410        assert_eq!(Flattener::new().flatten(&obj).unwrap(), json!({}));
411    }
412
413    /// Ensure that empty arrays are preserved if so configured
414    #[test]
415    fn empty_array_preserved() {
416        let obj = json!({"key": [], "a": {}});
417        assert_eq!(
418            Flattener::new()
419                .set_preserve_empty_arrays(true)
420                .flatten(&obj)
421                .unwrap(),
422            json!({"key": []})
423        );
424    }
425
426    /// Ensure that empty objects are preserved if so configured
427    #[test]
428    fn empty_object_preserved() {
429        let obj = json!({"key": {}, "a": []});
430        assert_eq!(
431            Flattener::new()
432                .set_preserve_empty_objects(true)
433                .flatten(&obj)
434                .unwrap(),
435            json!({"key": {}})
436        );
437    }
438
439    /// Ensure that all the end values of the JSON object that are either `[]` or `{}` are preserved
440    /// if so configured
441    #[test]
442    fn empty_objects_and_arrays_preserved() {
443        let obj = json!({
444            "key": {
445                "key2": {},
446                "key3": [[], {}, {"k": {}, "q": []}]
447            }
448        });
449        assert_eq!(
450            Flattener::new()
451                .set_preserve_empty_arrays(true)
452                .set_preserve_empty_objects(true)
453                .flatten(&obj)
454                .unwrap(),
455            json!({
456                "key.key2": {},
457                "key.key3.0": [],
458                "key.key3.1": {},
459                "key.key3.2.k": {},
460                "key.key3.2.q": [],
461            })
462        );
463    }
464
465    #[test]
466    fn nested_object_with_empty_array_and_string() {
467        let obj = json!({"key": {"key2": [], "key3": "a"}});
468        assert_eq!(
469            Flattener::new().flatten(&obj).unwrap(),
470            json!({"key.key3": "a"})
471        );
472    }
473
474    #[test]
475    fn nested_object_with_empty_object_and_string() {
476        let obj = json!({"key": {"key2": {}, "key3": "a"}});
477        assert_eq!(
478            Flattener::new().flatten(&obj).unwrap(),
479            json!({"key.key3": "a"})
480        );
481    }
482
483    #[test]
484    fn empty_string_as_key() {
485        let obj = json!({"key": {"": "a"}});
486        assert_eq!(
487            Flattener::new().flatten(&obj).unwrap(),
488            json!({"key.": "a"})
489        );
490    }
491
492    #[test]
493    fn empty_string_as_key_multiple_times() {
494        let obj = json!({"key": {"": {"": {"": "a"}}}});
495        assert_eq!(
496            Flattener::new().flatten(&obj).unwrap(),
497            json!({"key...": "a"})
498        );
499    }
500
501    /// Flattening only makes sense for objects. Passing something else must return an informative
502    /// error.
503    #[test]
504    fn first_level_must_be_an_object() {
505        let integer = json!(3);
506        let string = json!("");
507        let boolean = json!(false);
508        let null = json!(null);
509        let array = json!([1, 2, 3]);
510
511        for j in [integer, string, boolean, null, array] {
512            let res = Flattener::new().flatten(&j);
513            match res {
514                Err(Error::FirstLevelMustBeAnObject) => return, // Good
515                Ok(_) => panic!("This should have failed"),
516                _ => panic!("Wrong kind of error"),
517            }
518        }
519    }
520
521    #[test]
522    fn complex_array() {
523        let obj = json!({"a": [1, [2, [3, 4], 5], 6]});
524        assert_eq!(
525            Flattener::new().flatten(&obj).unwrap(),
526            json!({"a.0": 1, "a.2": 6, "a.1.0": 2, "a.1.1.0": 3, "a.1.1.1": 4, "a.1.2": 5}),
527        );
528    }
529}