nu_utils/
flatten_json.rs

1use serde_json::{Map, Value as SerdeValue, json};
2
3/// JsonFlattener is the main driver when flattening JSON
4/// # Examples
5/// ```
6/// use nu_utils;
7///
8/// let flattener = nu_utils::JsonFlattener { ..Default::default() };
9/// ```
10pub struct JsonFlattener<'a> {
11    /// Alternate separator used between keys when flattening
12    /// # Examples
13    /// ```
14    /// use nu_utils;
15    /// let flattener = nu_utils::JsonFlattener { separator: "_", ..Default::default()};
16    /// ```
17    pub separator: &'a str,
18    /// Opinionated flattening format that places values in an array if the object is nested inside an array
19    /// # Examples
20    /// ```
21    /// use nu_utils;
22    /// let flattener = nu_utils::JsonFlattener { alt_array_flattening: true, ..Default::default()};
23    /// ```
24    pub alt_array_flattening: bool,
25    /// Completely flatten JSON and keep array structure in the key when flattening
26    /// # Examples
27    /// ```
28    /// use nu_utils;
29    /// let flattener = nu_utils::JsonFlattener { preserve_arrays: true, ..Default::default()};
30    /// ```
31    pub preserve_arrays: bool,
32}
33
34impl Default for JsonFlattener<'_> {
35    fn default() -> Self {
36        JsonFlattener {
37            separator: ".",
38            alt_array_flattening: false,
39            preserve_arrays: false,
40        }
41    }
42}
43
44/// This implementation defines the core usage for the `JsonFlattener` structure.
45/// # Examples
46/// ```
47/// use nu_utils;
48/// use serde_json::json;
49///
50/// let flattener = nu_utils::JsonFlattener::new();
51/// let example = json!({
52///     "a": {
53///         "b": "c"
54///     }
55///  });
56///
57/// let flattened_example = flattener.flatten(&example);
58/// ```
59impl JsonFlattener<'_> {
60    /// Returns a flattener with the default arguments
61    /// # Examples
62    /// ```
63    /// use nu_utils;
64    ///
65    /// let flattener = nu_utils::JsonFlattener::new();
66    /// ```
67    pub fn new() -> Self {
68        JsonFlattener {
69            ..Default::default()
70        }
71    }
72
73    /// Flattens JSON variants into a JSON object
74    ///
75    /// # Arguments
76    ///
77    /// * `json` - A serde_json Value to flatten
78    ///
79    /// # Examples
80    /// ```
81    /// use nu_utils;
82    /// use serde_json::json;
83    ///
84    /// let flattener = nu_utils::JsonFlattener::new();
85    /// let example = json!({
86    ///     "name": "John Doe",
87    ///     "age": 43,
88    ///     "address": {
89    ///         "street": "10 Downing Street",
90    ///         "city": "London"
91    ///     },
92    ///     "phones": [
93    ///         "+44 1234567",
94    ///         "+44 2345678"
95    ///     ]
96    ///  });
97    ///
98    /// let flattened_example = flattener.flatten(&example);
99    /// ```
100    pub fn flatten(&self, json: &SerdeValue) -> SerdeValue {
101        let mut flattened_val = Map::<String, SerdeValue>::new();
102        match json {
103            SerdeValue::Array(obj_arr) => {
104                self.flatten_array(&mut flattened_val, &"".to_string(), obj_arr)
105            }
106            SerdeValue::Object(obj_val) => {
107                self.flatten_object(&mut flattened_val, None, obj_val, false)
108            }
109            _ => self.flatten_value(&mut flattened_val, &"".to_string(), json, false),
110        }
111        SerdeValue::Object(flattened_val)
112    }
113
114    fn flatten_object(
115        &self,
116        builder: &mut Map<String, SerdeValue>,
117        identifier: Option<&String>,
118        obj: &Map<String, SerdeValue>,
119        arr: bool,
120    ) {
121        for (k, v) in obj {
122            let expanded_identifier = identifier.map_or_else(
123                || k.clone(),
124                |identifier| format!("{identifier}{}{k}", self.separator),
125            );
126
127            if expanded_identifier.contains("span.start")
128                || expanded_identifier.contains("span.end")
129            {
130                continue;
131            }
132
133            let expanded_identifier = self.filter_known_keys(&expanded_identifier);
134
135            match v {
136                SerdeValue::Object(obj_val) => {
137                    self.flatten_object(builder, Some(&expanded_identifier), obj_val, arr)
138                }
139                SerdeValue::Array(obj_arr) => {
140                    self.flatten_array(builder, &expanded_identifier, obj_arr)
141                }
142                _ => self.flatten_value(builder, &expanded_identifier, v, arr),
143            }
144        }
145    }
146
147    fn flatten_array(
148        &self,
149        builder: &mut Map<String, SerdeValue>,
150        identifier: &String,
151        obj: &[SerdeValue],
152    ) {
153        for (k, v) in obj.iter().enumerate() {
154            let with_key = format!("{identifier}{}{k}", self.separator);
155            if with_key.contains("span.start") || with_key.contains("span.end") {
156                continue;
157            }
158
159            let with_key = self.filter_known_keys(&with_key);
160
161            match v {
162                SerdeValue::Object(obj_val) => self.flatten_object(
163                    builder,
164                    Some(if self.preserve_arrays {
165                        &with_key
166                    } else {
167                        identifier
168                    }),
169                    obj_val,
170                    self.alt_array_flattening,
171                ),
172                SerdeValue::Array(obj_arr) => self.flatten_array(
173                    builder,
174                    if self.preserve_arrays {
175                        &with_key
176                    } else {
177                        identifier
178                    },
179                    obj_arr,
180                ),
181                _ => self.flatten_value(
182                    builder,
183                    if self.preserve_arrays {
184                        &with_key
185                    } else {
186                        identifier
187                    },
188                    v,
189                    self.alt_array_flattening,
190                ),
191            }
192        }
193    }
194
195    fn flatten_value(
196        &self,
197        builder: &mut Map<String, SerdeValue>,
198        identifier: &String,
199        obj: &SerdeValue,
200        arr: bool,
201    ) {
202        if let Some(v) = builder.get_mut(identifier) {
203            if let Some(arr) = v.as_array_mut() {
204                arr.push(obj.clone());
205            } else {
206                let new_val = json!(vec![v, obj]);
207                builder.remove(identifier);
208                builder.insert(identifier.to_string(), new_val);
209            }
210        } else {
211            builder.insert(
212                identifier.to_string(),
213                if arr {
214                    json!(vec![obj.clone()])
215                } else {
216                    obj.clone()
217                },
218            );
219        }
220    }
221
222    fn filter_known_keys(&self, key: &str) -> String {
223        let mut filtered_key = key.to_string();
224        if filtered_key.contains(".String.val") {
225            filtered_key = filtered_key.replace(".String.val", "");
226        }
227        if filtered_key.contains(".Record.val") {
228            filtered_key = filtered_key.replace(".Record.val", "");
229        }
230        if filtered_key.contains(".List.vals") {
231            filtered_key = filtered_key.replace(".List.vals", "");
232        }
233        if filtered_key.contains(".Int.val") {
234            filtered_key = filtered_key.replace(".Int.val", "");
235        }
236        if filtered_key.contains(".Bool.val") {
237            filtered_key = filtered_key.replace(".Bool.val", "");
238        }
239        if filtered_key.contains(".Truncate.suffix") {
240            filtered_key = filtered_key.replace(".Truncate.suffix", ".truncating_suffix");
241        }
242        if filtered_key.contains(".RowCount") {
243            filtered_key = filtered_key.replace(".RowCount", "");
244        }
245        if filtered_key.contains(".Wrap.try_to_keep_words") {
246            filtered_key =
247                filtered_key.replace(".Wrap.try_to_keep_words", ".wrapping_try_keep_words");
248        }
249        // For now, let's skip replacing these because they tell us which
250        // numbers are closures and blocks which is useful for extracting the content
251        // if filtered_key.contains(".Closure.val") {
252        //     filtered_key = filtered_key.replace(".Closure.val", "");
253        // }
254        // if filtered_key.contains(".block_id") {
255        //     filtered_key = filtered_key.replace(".block_id", "");
256        // }
257        filtered_key
258    }
259}