vrl/stdlib/
flatten.rs

1use std::collections::btree_map;
2
3use crate::compiler::prelude::*;
4use std::sync::LazyLock;
5
6static DEFAULT_SEPARATOR: LazyLock<Value> = LazyLock::new(|| Value::Bytes(Bytes::from(".")));
7
8static PARAMETERS: LazyLock<Vec<Parameter>> = LazyLock::new(|| {
9    vec![
10        Parameter::required(
11            "value",
12            kind::OBJECT | kind::ARRAY,
13            "The array or object to flatten.",
14        ),
15        Parameter::optional(
16            "separator",
17            kind::BYTES,
18            "The separator to join nested keys",
19        )
20        .default(&DEFAULT_SEPARATOR),
21    ]
22});
23
24fn flatten(value: Value, separator: &Value) -> Resolved {
25    let separator = separator.try_bytes_utf8_lossy()?;
26
27    match value {
28        Value::Array(arr) => Ok(Value::Array(
29            ArrayFlatten::new(arr.iter()).cloned().collect(),
30        )),
31        Value::Object(map) => Ok(Value::Object(
32            MapFlatten::new(map.iter(), &separator)
33                .map(|(k, v)| (k, v.clone()))
34                .collect(),
35        )),
36        value => Err(ValueError::Expected {
37            got: value.kind(),
38            expected: Kind::array(Collection::any()) | Kind::object(Collection::any()),
39        }
40        .into()),
41    }
42}
43
44#[derive(Clone, Copy, Debug)]
45pub struct Flatten;
46
47impl Function for Flatten {
48    fn identifier(&self) -> &'static str {
49        "flatten"
50    }
51
52    fn usage(&self) -> &'static str {
53        "Flattens the `value` into a single-level representation."
54    }
55
56    fn category(&self) -> &'static str {
57        Category::Enumerate.as_ref()
58    }
59
60    fn return_kind(&self) -> u16 {
61        kind::ARRAY | kind::OBJECT
62    }
63
64    fn return_rules(&self) -> &'static [&'static str] {
65        &["The return type matches the `value` type."]
66    }
67
68    fn parameters(&self) -> &'static [Parameter] {
69        PARAMETERS.as_slice()
70    }
71
72    fn examples(&self) -> &'static [Example] {
73        &[
74            example! {
75                title: "Flatten array",
76                source: "flatten([1, [2, 3, 4], [5, [6, 7], 8], 9])",
77                result: Ok("[1, 2, 3, 4, 5, 6, 7, 8, 9]"),
78            },
79            example! {
80                title: "Flatten object",
81                source: indoc! {r#"
82                    flatten({
83                        "parent1": {
84                            "child1": 1,
85                            "child2": 2
86                        },
87                        "parent2": {
88                            "child3": 3
89                        }
90                    })
91                "#},
92                result: Ok(r#"{ "parent1.child1": 1, "parent1.child2": 2, "parent2.child3": 3 }"#),
93            },
94            example! {
95                title: "Flatten object with custom separator",
96                source: r#"flatten({ "foo": { "bar": true }}, "_")"#,
97                result: Ok(r#"{ "foo_bar": true }"#),
98            },
99        ]
100    }
101
102    fn compile(
103        &self,
104        _state: &state::TypeState,
105        _ctx: &mut FunctionCompileContext,
106        arguments: ArgumentList,
107    ) -> Compiled {
108        let separator = arguments.optional("separator");
109        let value = arguments.required("value");
110        Ok(FlattenFn { value, separator }.as_expr())
111    }
112}
113
114#[derive(Debug, Clone)]
115struct FlattenFn {
116    value: Box<dyn Expression>,
117    separator: Option<Box<dyn Expression>>,
118}
119
120impl FunctionExpression for FlattenFn {
121    fn resolve(&self, ctx: &mut Context) -> Resolved {
122        let value = self.value.resolve(ctx)?;
123        let separator = self
124            .separator
125            .map_resolve_with_default(ctx, || DEFAULT_SEPARATOR.clone())?;
126
127        flatten(value, &separator)
128    }
129
130    fn type_def(&self, state: &state::TypeState) -> TypeDef {
131        let td = self.value.type_def(state);
132
133        if td.is_array() {
134            TypeDef::array(Collection::any())
135        } else {
136            TypeDef::object(Collection::any())
137        }
138    }
139}
140
141/// An iterator to walk over maps allowing us to flatten nested maps to a single level.
142struct MapFlatten<'a> {
143    values: btree_map::Iter<'a, KeyString, Value>,
144    separator: &'a str,
145    inner: Option<Box<MapFlatten<'a>>>,
146    parent: Option<KeyString>,
147}
148
149impl<'a> MapFlatten<'a> {
150    fn new(values: btree_map::Iter<'a, KeyString, Value>, separator: &'a str) -> Self {
151        Self {
152            values,
153            separator,
154            inner: None,
155            parent: None,
156        }
157    }
158
159    fn new_from_parent(
160        parent: KeyString,
161        values: btree_map::Iter<'a, KeyString, Value>,
162        separator: &'a str,
163    ) -> Self {
164        Self {
165            values,
166            separator,
167            inner: None,
168            parent: Some(parent),
169        }
170    }
171
172    /// Returns the key with the parent prepended.
173    fn new_key(&self, key: &str) -> KeyString {
174        match self.parent {
175            None => key.to_string().into(),
176            Some(ref parent) => format!("{parent}{}{key}", self.separator).into(),
177        }
178    }
179}
180
181impl<'a> std::iter::Iterator for MapFlatten<'a> {
182    type Item = (KeyString, &'a Value);
183
184    fn next(&mut self) -> Option<Self::Item> {
185        if let Some(ref mut inner) = self.inner {
186            let next = inner.next();
187            match next {
188                Some(_) => return next,
189                None => self.inner = None,
190            }
191        }
192
193        let next = self.values.next();
194        match next {
195            Some((key, Value::Object(value))) => {
196                self.inner = Some(Box::new(MapFlatten::new_from_parent(
197                    self.new_key(key),
198                    value.iter(),
199                    self.separator,
200                )));
201                self.next()
202            }
203            Some((key, value)) => Some((self.new_key(key), value)),
204            None => None,
205        }
206    }
207}
208
209/// Create an iterator that can walk a tree of Array values.
210/// This can be used to flatten the array.
211struct ArrayFlatten<'a> {
212    values: std::slice::Iter<'a, Value>,
213    inner: Option<Box<ArrayFlatten<'a>>>,
214}
215
216impl<'a> ArrayFlatten<'a> {
217    fn new(values: std::slice::Iter<'a, Value>) -> Self {
218        ArrayFlatten {
219            values,
220            inner: None,
221        }
222    }
223}
224
225impl<'a> std::iter::Iterator for ArrayFlatten<'a> {
226    type Item = &'a Value;
227
228    fn next(&mut self) -> Option<Self::Item> {
229        // Iterate over our inner list first.
230        if let Some(ref mut inner) = self.inner {
231            let next = inner.next();
232            match next {
233                Some(_) => return next,
234                None => {
235                    // The inner list has been exhausted.
236                    self.inner = None;
237                }
238            }
239        }
240
241        // Then iterate over our values.
242        let next = self.values.next();
243        match next {
244            Some(Value::Array(next)) => {
245                // Create a new iterator for this child list.
246                self.inner = Some(Box::new(ArrayFlatten::new(next.iter())));
247                self.next()
248            }
249            _ => next,
250        }
251    }
252}
253
254#[cfg(test)]
255mod test {
256    use super::*;
257    use crate::value;
258
259    test_function![
260        flatten => Flatten;
261
262        array {
263            args: func_args![value: value!([42])],
264            want: Ok(value!([42])),
265            tdef: TypeDef::array(Collection::any()),
266        }
267
268        nested_array {
269            args: func_args![value: value!([42, [43, 44]])],
270            want: Ok(value!([42, 43, 44])),
271            tdef: TypeDef::array(Collection::any()),
272        }
273
274        nested_empty_array {
275            args: func_args![value: value!([42, [], 43])],
276            want: Ok(value!([42, 43])),
277            tdef: TypeDef::array(Collection::any()),
278        }
279
280        double_nested_array {
281            args: func_args![value: value!([42, [43, 44, [45, 46]]])],
282            want: Ok(value!([42, 43, 44, 45, 46])),
283            tdef: TypeDef::array(Collection::any()),
284        }
285
286        two_arrays {
287            args: func_args![value: value!([[42, 43], [44, 45]])],
288            want: Ok(value!([42, 43, 44, 45])),
289            tdef: TypeDef::array(Collection::any()),
290        }
291
292        map {
293            args: func_args![value: value!({parent: "child"})],
294            want: Ok(value!({parent: "child"})),
295            tdef: TypeDef::object(Collection::any()),
296        }
297
298        nested_map {
299            args: func_args![value: value!({parent: {child1: 1, child2: 2}, key: "val"})],
300            want: Ok(value!({"parent.child1": 1, "parent.child2": 2, key: "val"})),
301            tdef: TypeDef::object(Collection::any()),
302        }
303
304        nested_map_with_separator {
305            args: func_args![value: value!({parent: {child1: 1, child2: 2}, key: "val"}), separator: "_"],
306            want: Ok(value!({"parent_child1": 1, "parent_child2": 2, key: "val"})),
307            tdef: TypeDef::object(Collection::any()),
308        }
309
310        double_nested_map {
311            args: func_args![value: value!({
312                parent: {
313                    child1: 1,
314                    child2: { grandchild1: 1, grandchild2: 2 },
315                },
316                key: "val",
317            })],
318            want: Ok(value!({
319                "parent.child1": 1,
320                "parent.child2.grandchild1": 1,
321                "parent.child2.grandchild2": 2,
322                key: "val",
323            })),
324            tdef: TypeDef::object(Collection::any()),
325        }
326
327        map_and_array {
328            args: func_args![value: value!({
329                parent: {
330                    child1: [1, [2, 3]],
331                    child2: {grandchild1: 1, grandchild2: [1, [2, 3], 4]},
332                },
333                key: "val",
334            })],
335            want: Ok(value!({
336                "parent.child1": [1, [2, 3]],
337                "parent.child2.grandchild1": 1,
338                "parent.child2.grandchild2": [1, [2, 3], 4],
339                key: "val",
340            })),
341            tdef: TypeDef::object(Collection::any()),
342        }
343
344        map_and_array_with_separator {
345            args: func_args![value: value!({
346                parent: {
347                    child1: [1, [2, 3]],
348                    child2: {grandchild1: 1, grandchild2: [1, [2, 3], 4]},
349                },
350                key: "val",
351            }), separator: "_"],
352            want: Ok(value!({
353                "parent_child1": [1, [2, 3]],
354                "parent_child2_grandchild1": 1,
355                "parent_child2_grandchild2": [1, [2, 3], 4],
356                key: "val",
357            })),
358            tdef: TypeDef::object(Collection::any()),
359        }
360
361        // If the root object is an array, child maps are not flattened.
362        root_array {
363            args: func_args![value: value!([
364                { parent1: { child1: 1, child2: 2 } },
365                [
366                    { parent2: { child3: 3, child4: 4 } },
367                    { parent3: { child5: 5 } },
368                ],
369            ])],
370            want: Ok(value!([
371                { parent1: { child1: 1, child2: 2 } },
372                { parent2: { child3: 3, child4: 4 } },
373                { parent3: { child5: 5 } },
374            ])),
375            tdef: TypeDef::array(Collection::any()),
376        }
377
378        triple_nested_map {
379            args: func_args![value: value!({
380                parent1: {
381                    child1: { grandchild1: 1 },
382                    child2: { grandchild2: 2, grandchild3: 3 },
383                },
384                parent2: 4,
385            })],
386            want: Ok(value!({
387                "parent1.child1.grandchild1": 1,
388                "parent1.child2.grandchild2": 2,
389                "parent1.child2.grandchild3": 3,
390                parent2: 4,
391            })),
392            tdef: TypeDef::object(Collection::any()),
393        }
394    ];
395}