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}