vrl/stdlib/
unflatten.rs

1use itertools::Itertools;
2
3use crate::compiler::prelude::*;
4use std::sync::LazyLock;
5
6static DEFAULT_SEPARATOR: LazyLock<Value> = LazyLock::new(|| Value::Bytes(Bytes::from(".")));
7static DEFAULT_RECURSIVE: LazyLock<Value> = LazyLock::new(|| Value::Boolean(true));
8
9static PARAMETERS: LazyLock<Vec<Parameter>> = LazyLock::new(|| {
10    vec![
11        Parameter::required("value", kind::OBJECT, "The array or object to unflatten."),
12        Parameter::optional(
13            "separator",
14            kind::BYTES,
15            "The separator to split flattened keys.",
16        )
17        .default(&DEFAULT_SEPARATOR),
18        Parameter::optional(
19            "recursive",
20            kind::BOOLEAN,
21            "Whether to recursively unflatten the object values.",
22        )
23        .default(&DEFAULT_RECURSIVE),
24    ]
25});
26
27fn unflatten(value: Value, separator: &Value, recursive: Value) -> Resolved {
28    let separator = separator.try_bytes_utf8_lossy()?.into_owned();
29    let recursive = recursive.try_boolean()?;
30    let map = value.try_object()?;
31    Ok(do_unflatten(map.into(), &separator, recursive))
32}
33
34fn do_unflatten(value: Value, separator: &str, recursive: bool) -> Value {
35    match value {
36        Value::Object(map) => do_unflatten_entries(map, separator, recursive).into(),
37        // Note that objects inside arrays are not unflattened
38        _ => value,
39    }
40}
41
42fn do_unflatten_entries<I>(entries: I, separator: &str, recursive: bool) -> ObjectMap
43where
44    I: IntoIterator<Item = (KeyString, Value)>,
45{
46    let grouped = entries
47        .into_iter()
48        .map(|(key, value)| {
49            let (head, rest) = match key.split_once(separator) {
50                Some((key, rest)) => (key.to_string().into(), Some(rest.to_string().into())),
51                None => (key.clone(), None),
52            };
53            (head, rest, value)
54        })
55        .into_group_map_by(|(head, _, _)| head.clone());
56
57    grouped
58        .into_iter()
59        .map(|(key, mut values)| {
60            if values.len() == 1 {
61                match values.pop().expect("exactly one element") {
62                    (_, None, value) => {
63                        let value = if recursive {
64                            do_unflatten(value, separator, recursive)
65                        } else {
66                            value
67                        };
68                        return (key, value);
69                    }
70                    (_, Some(rest), value) => {
71                        let result = do_unflatten_entry((rest, value), separator, recursive);
72                        return (key, result);
73                    }
74                }
75            }
76
77            let new_entries = values
78                .into_iter()
79                .filter_map(|(_, rest, value)| {
80                    // In this case, there is more than one value prefixed with the same key
81                    // and therefore there must be nested values, so we can't set a single top-level value
82                    // and we must filter it out.
83                    // Example input of this case:
84                    // {
85                    //    "a.b": 1,
86                    //    "a": 2
87                    // }
88                    // Here, we will have two items grouped by "a",
89                    // one will have `"b"` as rest and the other will have `None`.
90                    // We have to filter the second, as we can't set the second value
91                    // as the value of the entry `"a"` (considered the top-level key at this level)
92                    rest.map(|rest| (rest, value))
93                })
94                .collect::<Vec<_>>();
95            let result = do_unflatten_entries(new_entries, separator, recursive);
96            (key, result.into())
97        })
98        .collect()
99}
100
101// Optimization in the case we have to flatten objects like
102// { "a.b.c.d": 1 }
103// and avoid doing recursive calls to `do_unflatten_entries` with a single entry every time
104fn do_unflatten_entry(entry: (KeyString, Value), separator: &str, recursive: bool) -> Value {
105    let (key, value) = entry;
106    let keys = key.split(separator).map(Into::into).collect::<Vec<_>>();
107    let mut result = if recursive {
108        do_unflatten(value, separator, recursive)
109    } else {
110        value
111    };
112    for key in keys.into_iter().rev() {
113        result = Value::Object(ObjectMap::from_iter([(key, result)]));
114    }
115    result
116}
117
118#[derive(Clone, Copy, Debug)]
119pub struct Unflatten;
120
121impl Function for Unflatten {
122    fn identifier(&self) -> &'static str {
123        "unflatten"
124    }
125
126    fn usage(&self) -> &'static str {
127        "Unflattens the `value` into a nested representation."
128    }
129
130    fn category(&self) -> &'static str {
131        Category::Enumerate.as_ref()
132    }
133
134    fn return_kind(&self) -> u16 {
135        kind::OBJECT
136    }
137
138    fn parameters(&self) -> &'static [Parameter] {
139        PARAMETERS.as_slice()
140    }
141
142    fn examples(&self) -> &'static [Example] {
143        &[
144            example! {
145                title: "Unflatten",
146                source: indoc! {r#"
147                    unflatten({
148                        "foo.bar.baz": true,
149                        "foo.bar.qux": false,
150                        "foo.quux": 42
151                    })
152                "#},
153                result: Ok(indoc! {r#"
154                    {
155                        "foo": {
156                            "bar": {
157                                "baz": true,
158                                "qux": false
159                            },
160                            "quux": 42
161                        }
162                    }
163                    "#}),
164            },
165            example! {
166                title: "Unflatten recursively",
167
168                source: indoc! {r#"
169                    unflatten({
170                        "flattened.parent": {
171                            "foo.bar": true,
172                            "foo.baz": false
173                        }
174                    })
175                "#},
176                result: Ok(indoc! {r#"
177                    {
178                        "flattened": {
179                            "parent": {
180                                "foo": {
181                                    "bar": true,
182                                    "baz": false
183                                }
184                            }
185                        }
186                    }
187                    "#}),
188            },
189            example! {
190                title: "Unflatten non-recursively",
191                source: indoc! {r#"
192                    unflatten({
193                        "flattened.parent": {
194                            "foo.bar": true,
195                            "foo.baz": false
196                        }
197                    }, recursive: false)
198                "#},
199                result: Ok(indoc! {r#"
200                    {
201                        "flattened": {
202                            "parent": {
203                                "foo.bar": true,
204                                "foo.baz": false
205                            }
206                        }
207                    }
208                    "#}),
209            },
210            example! {
211                title: "Ignore inconsistent keys values",
212                source: indoc! {r#"
213                    unflatten({
214                        "a": 3,
215                        "a.b": 2,
216                        "a.c": 4
217                    })
218                "#},
219                result: Ok(indoc! {r#"
220                    {
221                        "a": {
222                            "b": 2,
223                            "c": 4
224                        }
225                    }
226                    "#}),
227            },
228            example! {
229                title: "Unflatten with custom separator",
230                source: r#"unflatten({ "foo_bar": true }, "_")"#,
231                result: Ok(r#"{"foo": { "bar": true }}"#),
232            },
233        ]
234    }
235
236    fn compile(
237        &self,
238        _state: &state::TypeState,
239        _ctx: &mut FunctionCompileContext,
240        arguments: ArgumentList,
241    ) -> Compiled {
242        let value = arguments.required("value");
243        let separator = arguments.optional("separator");
244        let recursive = arguments.optional("recursive");
245
246        Ok(UnflattenFn {
247            value,
248            separator,
249            recursive,
250        }
251        .as_expr())
252    }
253}
254
255#[derive(Debug, Clone)]
256struct UnflattenFn {
257    value: Box<dyn Expression>,
258    separator: Option<Box<dyn Expression>>,
259    recursive: Option<Box<dyn Expression>>,
260}
261
262impl FunctionExpression for UnflattenFn {
263    fn resolve(&self, ctx: &mut Context) -> Resolved {
264        let value = self.value.resolve(ctx)?;
265        let separator = self
266            .separator
267            .map_resolve_with_default(ctx, || DEFAULT_SEPARATOR.clone())?;
268        let recursive = self
269            .recursive
270            .map_resolve_with_default(ctx, || DEFAULT_RECURSIVE.clone())?;
271
272        unflatten(value, &separator, recursive)
273    }
274
275    fn type_def(&self, _: &TypeState) -> TypeDef {
276        TypeDef::object(Collection::any())
277    }
278}
279
280#[cfg(test)]
281mod test {
282    use super::*;
283    use crate::value;
284
285    test_function![
286        unflatten => Unflatten;
287
288        map {
289            args: func_args![value: value!({parent: "child"})],
290            want: Ok(value!({parent: "child"})),
291            tdef: TypeDef::object(Collection::any()),
292        }
293
294        nested_map {
295            args: func_args![value: value!({"parent.child1": 1, "parent.child2": 2, key: "val"})],
296            want: Ok(value!({parent: {child1: 1, child2: 2}, key: "val"})),
297            tdef: TypeDef::object(Collection::any()),
298        }
299
300        nested_map_with_separator {
301            args: func_args![value: value!({"parent_child1": 1, "parent_child2": 2, key: "val"}), separator: "_"],
302            want: Ok(value!({parent: {child1: 1, child2: 2}, key: "val"})),
303            tdef: TypeDef::object(Collection::any()),
304        }
305
306        double_nested_map {
307            args: func_args![value: value!({
308                "parent.child1": 1,
309                "parent.child2.grandchild1": 1,
310                "parent.child2.grandchild2": 2,
311                key: "val",
312            })],
313            want: Ok(value!({
314                parent: {
315                    child1: 1,
316                    child2: { grandchild1: 1, grandchild2: 2 },
317                },
318                key: "val",
319            })),
320            tdef: TypeDef::object(Collection::any()),
321        }
322
323        // Not only keys at first level are unflattened
324        double_inner_nested_map_not_recursive {
325            args: func_args![value: value!({
326                "parent.children": {"child1":1, "child2.grandchild1": 1, "child2.grandchild2": 2 },
327                key: "val",
328            }), recursive: false],
329            want: Ok(value!({
330                parent: {
331                    children: {child1: 1, "child2.grandchild1": 1, "child2.grandchild2": 2 }
332                },
333                key: "val",
334            })),
335            tdef: TypeDef::object(Collection::any()),
336        }
337
338        // Not only keys at first level are unflattened
339        double_inner_nested_map_recursive {
340            args: func_args![value: value!({
341                "parent.children": {child1:1, "child2.grandchild1": 1, "child2.grandchild2": 2 },
342                key: "val",
343            })],
344            want: Ok(value!({
345                parent: {
346                    children: {
347                        child1: 1,
348                        child2: { grandchild1: 1, grandchild2: 2 },
349                    },
350                },
351                key: "val",
352            })),
353            tdef: TypeDef::object(Collection::any()),
354        }
355
356        map_and_array {
357            args: func_args![value: value!({
358                "parent.child1": [1, [2, 3]],
359                "parent.child2.grandchild1": 1,
360                "parent.child2.grandchild2": [1, [2, 3], 4],
361                key: "val",
362            })],
363            want: Ok(value!({
364                parent: {
365                    child1: [1, [2, 3]],
366                    child2: {grandchild1: 1, grandchild2: [1, [2, 3], 4]},
367                },
368                key: "val",
369            })),
370            tdef: TypeDef::object(Collection::any()),
371        }
372
373        map_and_array_with_separator {
374            args: func_args![value: value!({
375                "parent_child1": [1, [2, 3]],
376                "parent_child2_grandchild1": 1,
377                "parent_child2_grandchild2": [1, [2, 3], 4],
378                key: "val",
379            }), separator: "_"],
380            want: Ok(value!({
381                parent: {
382                    child1: [1, [2, 3]],
383                    child2: {grandchild1: 1, grandchild2: [1, [2, 3], 4]},
384                },
385                key: "val",
386            })),
387            tdef: TypeDef::object(Collection::any()),
388        }
389
390        // Objects inside arrays are not unflattened
391        objects_inside_arrays {
392            args: func_args![value: value!({
393                "parent": [{"child1":1},{"child2.grandchild1": 1, "child2.grandchild2": 2 }],
394                key: "val",
395            })],
396            want: Ok(value!({
397                "parent": [{"child1":1},{"child2.grandchild1": 1, "child2.grandchild2": 2 }],
398                key: "val",
399            })),
400            tdef: TypeDef::object(Collection::any()),
401        }
402
403        triple_nested_map {
404            args: func_args![value: value!({
405                "parent1.child1.grandchild1": 1,
406                "parent1.child2.grandchild2": 2,
407                "parent1.child2.grandchild3": 3,
408                parent2: 4,
409            })],
410            want: Ok(value!({
411                parent1: {
412                    child1: { grandchild1: 1 },
413                    child2: { grandchild2: 2, grandchild3: 3 },
414                },
415                parent2: 4,
416            })),
417            tdef: TypeDef::object(Collection::any()),
418        }
419
420        single_very_nested_map{
421            args: func_args![value: value!({
422                "a.b.c.d.e.f.g": 1,
423            })],
424            want: Ok(value!({
425                a: {
426                    b: {
427                        c: {
428                            d: {
429                                e: {
430                                    f: {
431                                        g: 1,
432                                    },
433                                },
434                            },
435                        },
436                    },
437                },
438            })),
439            tdef: TypeDef::object(Collection::any()),
440        }
441
442        consecutive_separators {
443            args: func_args![value: value!({
444                "a..b": 1,
445                "a...c": 2,
446            })],
447            want: Ok(value!({
448                a: {
449                    "": {
450                        b: 1,
451                        "": {
452                            c: 2,
453                        },
454                    },
455                },
456            })),
457            tdef: TypeDef::object(Collection::any()),
458        }
459
460        traling_separator{
461            args: func_args![value: value!({
462                "a.": 1,
463            })],
464            want: Ok(value!({
465                a: {
466                    "": 1,
467                },
468            })),
469            tdef: TypeDef::object(Collection::any()),
470        }
471
472        consecutive_trailing_separator{
473            args: func_args![value: value!({
474                "a..": 1,
475            })],
476            want: Ok(value!({
477                a: {
478                    "": {
479                        "": 1,
480                    }
481                },
482            })),
483            tdef: TypeDef::object(Collection::any()),
484        }
485
486        filter_out_top_level_value_when_multiple_values {
487            args: func_args![value: value!({
488                "a.b": 1,
489                "a": 2,
490            })],
491            want: Ok(value!({
492                a: { b: 1 },
493            })),
494            tdef: TypeDef::object(Collection::any()),
495        }
496    ];
497}