Search code examples
dataweavemule-esb

Dataweave combine repeated element of xml into object with path to node as keys


I have the following XML and its json counter part in dw playground:

%dw 2.0
output application/json
---
payload

<insurance>
  <info>
    <pid>101</pid>
    <pid>102</pid>
    <pid>103</pid>
    <pid>104</pid>
  </info>
  <details>
    <rid>21</rid>
    <rid>22</rid>
    <rid>23</rid>
  </details>
</insurance>

{
  "insurance": {
    "info": {
      "pid": "101",
      "pid": "102",
      "pid": "103",
      "pid": "104"
    },
    "details": {
      "rid": "21",
      "rid": "22",
      "rid": "23"
    }
  }
}

My requirement is to convert it into {"root to node path": "array of values"} . Given below is my attempt that does not work, since DW converts repeated fields into individual keys and not an array.

**EXPECTED:**
{
  "root-insurance-info-pid": [101, 102, 103, 104],
  "root-insurace-details-rid": [21, 22, 23]
}

**ACTUAL:**
{
  "root-insurance-info-pid": 101,
  "root-insurance-info-pid": 102,
   ...
  "root-insurace-details-rid": 23
}

fun generate(obj ,parentKey) = 
    obj match {
        case is Object -> obj mapObject ((value, key, index) -> 
                generate(value, parentKey ++ "-" ++ (key as String))
            )
        case is Array -> {(parentKey): obj}
        else -> {(parentKey): obj}
    }
---
generate(payload, "root")

  1. Is there a way to achieve this, making sure everything remains dynamic. Since the nested level can vary.
  2. What modifications can be made on input XML if any?

Solution

  • The problem is that in XML there is no concept of array. Those are just lists of elements with the same key under the same parent. If you can add a attribute to the XML input to indicate which elements are to be considered 'arrays' for the script, then it is easier to process them. Assuming for those 'arrays' all share the same key:

    %dw 2.0
    output application/json
    fun getFirstKey(o)=keysOf(o)[0] // assumes in an array element all subkeys are the same
    
    fun generate(obj ,parentKey) = 
        obj match {
            case is Object -> 
                if (obj.@array == "true") 
                    {(parentKey ++ "-" ++ getFirstKey(obj) as String): valuesOf(obj)}
                else obj mapObject ((value, key, index) -> generate(value, parentKey ++ "-" ++ (key as String)))
            case a is Array -> {(parentKey): a}
            else -> {(parentKey): obj}
        }
    ---
    generate(payload, "root")
    

    Output:

    {
      "root-insurance-info-pid": [
        "101",
        "102",
        "103",
        "104"
      ],
      "root-insurance-details-rid": [
        "21",
        "22",
        "23"
      ]
    }
    

    Without changing the XML, you could achieve the same result by adding checks for when an object has all its keys with the same name and each value is a basic type instead of more objects.