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 _ => 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 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
101fn 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 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 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 {
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}