vrl/stdlib/
object_from_array.rs

1use super::util::ConstOrExpr;
2use crate::compiler::prelude::*;
3
4fn make_object_1(values: Vec<Value>) -> Resolved {
5    values
6        .into_iter()
7        .filter_map(|kv| make_key_value(kv).transpose())
8        .collect::<Result<_, _>>()
9        .map(Value::Object)
10}
11
12fn make_object_2(keys: Vec<Value>, values: Vec<Value>) -> Resolved {
13    keys.into_iter()
14        .zip(values)
15        .filter_map(|(key, value)| {
16            make_key_string(key)
17                .transpose()
18                .map(|key| key.map(|key| (key, value)))
19        })
20        .collect::<Result<_, _>>()
21        .map(Value::Object)
22}
23
24fn make_key_value(value: Value) -> ExpressionResult<Option<(KeyString, Value)>> {
25    let array = value.try_array()?;
26    let mut iter = array.into_iter();
27    let Some(key) = iter.next() else {
28        return Err("array value too short".into());
29    };
30    Ok(make_key_string(key)?.map(|key| (key, iter.next().unwrap_or(Value::Null))))
31}
32
33fn make_key_string(key: Value) -> ExpressionResult<Option<KeyString>> {
34    match key {
35        Value::Bytes(key) => Ok(Some(String::from_utf8_lossy(&key).into())),
36        Value::Null => Ok(None),
37        _ => Err("object keys must be strings".into()),
38    }
39}
40
41#[derive(Clone, Copy, Debug)]
42pub struct ObjectFromArray;
43
44impl Function for ObjectFromArray {
45    fn identifier(&self) -> &'static str {
46        "object_from_array"
47    }
48
49    fn usage(&self) -> &'static str {
50        indoc! {"
51            Iterate over either one array of arrays or a pair of arrays and create an object out of all the key-value pairs contained in them.
52            With one array of arrays, any entries with no value use `null` instead.
53            Any keys that are `null` skip the  corresponding value.
54
55            If a single parameter is given, it must contain an array of all the input arrays.
56        "}
57    }
58
59    fn category(&self) -> &'static str {
60        Category::Object.as_ref()
61    }
62
63    fn internal_failure_reasons(&self) -> &'static [&'static str] {
64        &[
65            "`values` and `keys` must be arrays.",
66            "If `keys` is not present, `values` must contain only arrays.",
67        ]
68    }
69
70    fn return_kind(&self) -> u16 {
71        kind::OBJECT
72    }
73
74    fn return_rules(&self) -> &'static [&'static str] {
75        &[
76            "`object_from_array` is considered fallible in the following cases: if any of the parameters is not an array; if only the `value` parameter is present and it is not an array of arrays; or if any of the keys are not either a string or `null`.",
77        ]
78    }
79
80    fn parameters(&self) -> &'static [Parameter] {
81        const PARAMETERS: &[Parameter] = &[
82            Parameter::required(
83                "values",
84                kind::ARRAY,
85                "The first array of elements, or the array of input arrays if no other parameter is present.",
86            ),
87            Parameter::optional(
88                "keys",
89                kind::ARRAY,
90                "The second array of elements. If not present, the first parameter must contain all the arrays.",
91            ),
92        ];
93        PARAMETERS
94    }
95
96    fn examples(&self) -> &'static [Example] {
97        &[
98            example! {
99                title: "Create an object from one array",
100                source: r#"object_from_array([["one", 1], [null, 2], ["two", 3]])"#,
101                result: Ok(r#"{ "one": 1, "two": 3 }"#),
102            },
103            example! {
104                title: "Create an object from separate key and value arrays",
105                source: r#"object_from_array([1, 2, 3], keys: ["one", null, "two"])"#,
106                result: Ok(r#"{ "one": 1, "two": 3 }"#),
107            },
108            example! {
109                title: "Create an object from a separate arrays of keys and values",
110                source: r#"object_from_array(values: [1, null, true], keys: ["a", "b", "c"])"#,
111                result: Ok(r#"{"a": 1, "b": null, "c": true}"#),
112            },
113        ]
114    }
115
116    fn compile(
117        &self,
118        state: &TypeState,
119        _ctx: &mut FunctionCompileContext,
120        arguments: ArgumentList,
121    ) -> Compiled {
122        let values = ConstOrExpr::new(arguments.required("values"), state);
123        let keys = arguments
124            .optional("keys")
125            .map(|keys| ConstOrExpr::new(keys, state));
126
127        Ok(OFAFn { keys, values }.as_expr())
128    }
129}
130
131#[derive(Clone, Debug)]
132struct OFAFn {
133    keys: Option<ConstOrExpr>,
134    values: ConstOrExpr,
135}
136
137impl FunctionExpression for OFAFn {
138    fn resolve(&self, ctx: &mut Context) -> Resolved {
139        let values = self.values.resolve(ctx)?.try_array()?;
140        match &self.keys {
141            None => make_object_1(values),
142            Some(keys) => make_object_2(keys.resolve(ctx)?.try_array()?, values),
143        }
144    }
145
146    fn type_def(&self, _state: &TypeState) -> TypeDef {
147        TypeDef::object(Collection::any())
148    }
149}
150
151#[cfg(test)]
152mod tests {
153    use crate::value;
154
155    use super::*;
156
157    test_function![
158        object_from_array => ObjectFromArray;
159
160        makes_object_simple {
161            args: func_args![values: value!([["foo", 1], ["bar", 2]])],
162            want: Ok(value!({"foo": 1, "bar": 2})),
163            tdef: TypeDef::object(Collection::any()),
164        }
165
166        uses_keys_parameter {
167            args: func_args![keys: value!(["foo", "bar"]), values: value!([1, 2])],
168            want: Ok(value!({"foo": 1, "bar": 2})),
169            tdef: TypeDef::object(Collection::any()),
170        }
171
172        handles_missing_values {
173            args: func_args![values: value!([["foo", 1], ["bar"]])],
174            want: Ok(value!({"foo": 1, "bar": null})),
175            tdef: TypeDef::object(Collection::any()),
176        }
177
178        drops_extra_values {
179            args: func_args![values: value!([["foo", 1, 2, 3, 4]])],
180            want: Ok(value!({"foo": 1})),
181            tdef: TypeDef::object(Collection::any()),
182        }
183
184        errors_on_missing_keys {
185            args: func_args![values: value!([["foo", 1], []])],
186            want: Err("array value too short"),
187            tdef: TypeDef::object(Collection::any()),
188        }
189
190        skips_null_keys1 {
191            args: func_args![values: value!([["foo", 1], [null, 2], ["bar", 3]])],
192            want: Ok(value!({"foo": 1, "bar": 3})),
193            tdef: TypeDef::object(Collection::any()),
194        }
195
196        skips_null_keys2 {
197            args: func_args![values: value!([1, 2, 3]), keys: value!(["foo", null, "bar"])],
198            want: Ok(value!({"foo": 1, "bar": 3})),
199            tdef: TypeDef::object(Collection::any()),
200        }
201    ];
202}