flatten_serde_json/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use serde_json::{json, Map, Value};
4
5pub fn flatten(json: &Map<String, Value>) -> Map<String, Value> {
6    let mut obj = Map::new();
7    insert_object(&mut obj, None, json);
8    obj
9}
10
11fn insert_object(
12    base_json: &mut Map<String, Value>,
13    base_key: Option<&str>,
14    object: &Map<String, Value>,
15) {
16    for (key, value) in object {
17        let new_key = base_key.map_or_else(|| key.clone(), |base_key| format!("{base_key}.{key}"));
18
19        if let Some(array) = value.as_array() {
20            insert_array(base_json, &new_key, array);
21        } else if let Some(object) = value.as_object() {
22            insert_object(base_json, Some(&new_key), object);
23        } else {
24            insert_value(base_json, &new_key, value.clone());
25        }
26    }
27}
28
29fn insert_array(base_json: &mut Map<String, Value>, base_key: &str, array: &Vec<Value>) {
30    for value in array {
31        if let Some(object) = value.as_object() {
32            insert_object(base_json, Some(base_key), object);
33        } else if let Some(sub_array) = value.as_array() {
34            insert_array(base_json, base_key, sub_array);
35        } else {
36            insert_value(base_json, base_key, value.clone());
37        }
38    }
39}
40
41fn insert_value(base_json: &mut Map<String, Value>, key: &str, to_insert: Value) {
42    debug_assert!(!to_insert.is_object());
43    debug_assert!(!to_insert.is_array());
44
45    // does the field aleardy exists?
46    if let Some(value) = base_json.get_mut(key) {
47        // is it already an array
48        if let Some(array) = value.as_array_mut() {
49            array.push(to_insert);
50        // or is there a collision
51        } else {
52            let value = std::mem::take(value);
53            base_json[key] = json!([value, to_insert]);
54        }
55        // if it does not exist we can push the value untouched
56    } else {
57        base_json.insert(key.to_string(), json!(to_insert));
58    }
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64
65    #[test]
66    fn no_flattening() {
67        let mut base: Value = json!({
68          "id": "287947",
69          "title": "Shazam!",
70          "release_date": 1553299200,
71          "genres": [
72            "Action",
73            "Comedy",
74            "Fantasy"
75          ]
76        });
77        let json = std::mem::take(base.as_object_mut().unwrap());
78        let flat = flatten(&json);
79
80        println!(
81            "got:\n{}\nexpected:\n{}\n",
82            serde_json::to_string_pretty(&flat).unwrap(),
83            serde_json::to_string_pretty(&json).unwrap()
84        );
85
86        assert_eq!(flat, json);
87    }
88
89    #[test]
90    fn flatten_object() {
91        let mut base: Value = json!({
92          "a": {
93            "b": "c",
94            "d": "e",
95            "f": "g"
96          }
97        });
98        let json = std::mem::take(base.as_object_mut().unwrap());
99        let flat = flatten(&json);
100
101        assert_eq!(
102            &flat,
103            json!({
104                "a.b": "c",
105                "a.d": "e",
106                "a.f": "g"
107            })
108            .as_object()
109            .unwrap()
110        );
111    }
112
113    #[test]
114    fn flatten_array() {
115        let mut base: Value = json!({
116          "a": [
117            { "b": "c" },
118            { "b": "d" },
119            { "b": "e" },
120          ]
121        });
122        let json = std::mem::take(base.as_object_mut().unwrap());
123        let flat = flatten(&json);
124
125        assert_eq!(
126            &flat,
127            json!({
128                "a.b": ["c", "d", "e"],
129            })
130            .as_object()
131            .unwrap()
132        );
133
134        // here we must keep 42 in "a"
135        let mut base: Value = json!({
136          "a": [
137            42,
138            { "b": "c" },
139            { "b": "d" },
140            { "b": "e" },
141          ]
142        });
143        let json = std::mem::take(base.as_object_mut().unwrap());
144        let flat = flatten(&json);
145
146        assert_eq!(
147            &flat,
148            json!({
149                "a": 42,
150                "a.b": ["c", "d", "e"],
151            })
152            .as_object()
153            .unwrap()
154        );
155    }
156
157    #[test]
158    fn collision_with_object() {
159        let mut base: Value = json!({
160          "a": {
161            "b": "c",
162          },
163          "a.b": "d",
164        });
165        let json = std::mem::take(base.as_object_mut().unwrap());
166        let flat = flatten(&json);
167
168        assert_eq!(
169            &flat,
170            json!({
171                "a.b": ["c", "d"],
172            })
173            .as_object()
174            .unwrap()
175        );
176    }
177
178    #[test]
179    fn collision_with_array() {
180        let mut base: Value = json!({
181          "a": [
182            { "b": "c" },
183            { "b": "d", "c": "e" },
184            [35],
185          ],
186          "a.b": "f",
187        });
188        let json = std::mem::take(base.as_object_mut().unwrap());
189        let flat = flatten(&json);
190
191        assert_eq!(
192            &flat,
193            json!({
194                "a.b": ["c", "d", "f"],
195                "a.c": "e",
196                "a": 35,
197            })
198            .as_object()
199            .unwrap()
200        );
201    }
202
203    #[test]
204    fn flatten_nested_arrays() {
205        let mut base: Value = json!({
206          "a": [
207            ["b", "c"],
208            { "d": "e" },
209            ["f", "g"],
210            [
211                { "h": "i" },
212                { "d": "j" },
213            ],
214            ["k", "l"],
215          ]
216        });
217        let json = std::mem::take(base.as_object_mut().unwrap());
218        let flat = flatten(&json);
219
220        assert_eq!(
221            &flat,
222            json!({
223                "a": ["b", "c", "f", "g", "k", "l"],
224                "a.d": ["e", "j"],
225                "a.h": "i",
226            })
227            .as_object()
228            .unwrap()
229        );
230    }
231
232    #[test]
233    fn flatten_nested_arrays_and_objects() {
234        let mut base: Value = json!({
235          "a": [
236            "b",
237            ["c", "d"],
238            { "e": ["f", "g"] },
239            [
240                { "h": "i" },
241                { "e": ["j", { "z": "y" }] },
242            ],
243            ["l"],
244            "m",
245          ]
246        });
247        let json = std::mem::take(base.as_object_mut().unwrap());
248        let flat = flatten(&json);
249
250        println!("{}", serde_json::to_string_pretty(&flat).unwrap());
251
252        assert_eq!(
253            &flat,
254            json!({
255                "a": ["b", "c", "d", "l", "m"],
256                "a.e": ["f", "g", "j"],
257                "a.h": "i",
258                "a.e.z": "y",
259            })
260            .as_object()
261            .unwrap()
262        );
263    }
264}