Unflatten a JSON Object into nested JSON Object using Dataweave 2.0
You can try the following dataweave expression, based on this Apisero article, written by Mohammad Mazhar Ansari: https://apisero.com/property-to-yaml-file-conversion-using-mulesoft/
%dw 2.0var test = { "abc.def.ghi": "foo", "abc.def.jkl": "bar"}import * from dw::core::Stringsoutput application/jsonfun generateArray (obj) = obj pluck (v, k) -> (k): vfun isSubChildExists (key) = (((key) splitBy ("."))[1] != null)fun propToJSON(key, value) = if (isSubChildExists(key)) { (substringBefore(key, ".")) : propToJSON(substringAfter(key, "."), value)}else (key): valuefun arrToObj(arr) = arr reduce ((env, obj={}) -> obj ++ env)fun CombineObjBasedOnKey (Obj) =if (typeOf(Obj) == Array and sizeOf(Obj..) > 1)((Obj groupBy (item, index) -> keysOf(item)[0]) mapObject ((value, key, index) -> (if (typeOf(value) == Array) (key): CombineObjBasedOnKey(value..'$key') else if (typeOf(value) == String) value else (key): value) as Object))else Obj[0]---CombineObjBasedOnKey(generateArray(test) map ((item, index) -> item mapObject ((value, key, index) -> propToJSON((key), value))))
Output:
{ "abc": { "def": { "ghi": "foo", "jkl": "bar" } }}
This is similar to @olamiral solution, but simplified and supports arrays.
%dw 2.0output 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 itvar 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.
Another way to do this is to use the update operator that was introduce in 4.3. With this operator we can do upsert (insert the value when is not present or update if present) Using that and a reduce we can go from each of the part of the expression and do the proper update
%dw 2.0output application/jsonimport * from dw::util::Valuesfun upsert(object: {}, path:Array<String>, value: Any): Object = do { path match { case [] -> object case [x ~ xs] -> if(isEmpty(xs)) object update { case ."$(x)"! -> value } else object update { case selected at ."$(x)"! -> //selected is going to be null when the value is not present upsert(selected default {}, xs, value) } }}---payload pluck ((value, key, index) -> {key: key, value: value}) reduce ((item, resultObject = {} ) -> do { upsert(resultObject, (item.key as String splitBy '.') , item.value) })