Search code examples
jsonmuledataweavemulesoft

Unflatten a JSON Object into nested JSON Object using Dataweave 2.0


I have this Flattened Object

{
  "abc.def.ghi": "foo",
  "abc.def.jkl": "bar"
}

I want to write a dataweave to convert it into the original object, i.e.

{
  "abc": {
    "def": {
      "ghi": "foo",
      "jkl": "bar"
    }
  }
}

I am trying to avoid hard coding the keys because it is a quite large object, So I do not want something like this:

%dw 2.0
var test = {
    "abc.def.ghi": "foo",
    "abc.def.jkl": "bar"
}
output application/json
---
{
    abc: {
        def: {
            ghi: test."abc.def.ghi",
            jkl: test."abc.def.jkl"
        }
    }
}

Can I use some combination of available dataweave functions for achieving this?

Here is what I have tried so far:

%dw 2.0
var test = {
    "abc.def.ghi": "foo",
    "abc.def.jkl": "bar"
}
output application/json
---
test mapObject ((value, key) -> 
    (key as String splitBy  ".")[-1 to 0]  
         reduce ((item, acc = value) -> 
             (item): acc     
/*
   First item=ghi,acc=foo => acc = {ghi: "foo"} 
   next item=def, acc={ghi: "foo"} => acc={def:{ghi:"foo"}}
*/
    )
)

But this would generate some kind of separate pair of the nested JSON. Here is the output of the above code:

{
  "abc": {
    "def": {
      "ghi": "foo"
    }
  },
  "abc": {
    "def": {
      "jkl": "bar"
    }
  }
}

Solution

  • This is similar to @olamiral solution, but simplified and supports arrays.

    %dw 2.0
    output application/json
    
    // Creates a array of key-value tuples with the object structure. 
    // I was not able to use entriesOf() because I had to modify the key to split it
    var tuples = payload pluck ((value, key, index) -> 
        { 
            k: key splitBy("."), 
            v: value}
        )
    
    // Using groupBy, group the childs and maps to an object structure.
    fun flatToObject(tuples, index) =
        (tuples groupBy $.k[index]) mapObject ((groupedTuples, key, idx) -> 
            if(groupedTuples[0].k[index + 1]?) 
                // Has more levels
                { (key): flatToObject(groupedTuples,index + 1) }
            else 
                // It's a leaf
                { (key): if (sizeOf(groupedTuples.v) > 1)
                        // It has an array of values
                        groupedTuples.v 
                    else 
                        // It has a single value
                        groupedTuples.v[0]
                }
        )
    ---
    flatToObject(tuples,0)
    

    With this payload:

    {
        "abc.def.ghi": "foo",
        "abc.def.jkl": "bar",
        "abc.de.f": "bar2",
        "abc.def.jkm": "bar3",
        "abc.de.f": 45
    }
    

    It's producing this output:

    {
      "abc": {
        "def": {
          "ghi": "foo",
          "jkl": "bar",
          "jkm": "bar3"
        },
        "de": { 
          "f": ["bar2", 45]
        }
      }
    }
    

    This solution does not support mixing simple values and objects in the same array.