jsonxmlgroovysap-cpi

Converting XML to JSON using Groovy


I am trying to convert XML into desired JSON using Groovy in SAP CPI. I am not getting results as wanted using standard XML-to-JSON converter provided by SAP CPI. I have written below groovy code and it is giving me JSON as I wanted except the Array name.

def xml = """<Rows>
    <Values>
    <Value>
        <Name>DummyTest_1</Name>
        <Site.Value>3</Site.Value>
        <Description>Dummy PN For Test 1</Description>
        <Type.Value>N</Type.Value>
        <BuyerCode.Value/>,
      <PlannerCode.Value/>,
      <PlannerCode.Description/>,
      <ABCCode.Value>Default</ABCCode.Value>
        <UnitOfMeasure.Value>LB</UnitOfMeasure.Value>
        <SafetyStockQty>1</SafetyStockQty>
        <StdUnitCost>10</StdUnitCost>
        <AverageSellingPrice>15</AverageSellingPrice>
        <ProductFamily.Value/>
        <ProductGroup1>CHC</ProductGroup1>
        <IncrementalRule.Value>Default</IncrementalRule.Value>
        <MUEPoolNettingType.Value>Ignore</MUEPoolNettingType.Value>
        <PlanningCalendars.Value>Default</PlanningCalendars.Value>
        <SourceRule.Value>Default</SourceRule.Value>
    </Value>
    <Value>
        <Name>DummyTest_2</Name>
        <Site.Value>4</Site.Value>
        <Description>Dummy PN For Test 2</Description>
        <Type.Value>H</Type.Value>
        <BuyerCode.Value/>,
      <PlannerCode.Value/>,
      <PlannerCode.Description/>,
      <ABCCode.Value>Default2</ABCCode.Value>
        <UnitOfMeasure.Value>BL</UnitOfMeasure.Value>
        <SafetyStockQty>2</SafetyStockQty>
        <StdUnitCost>20</StdUnitCost>
        <AverageSellingPrice>16</AverageSellingPrice>
        <ProductFamily.Value/>
        <ProductGroup1>CHC</ProductGroup1>
        <IncrementalRule.Value>Default</IncrementalRule.Value>
        <MUEPoolNettingType.Value>Ignore</MUEPoolNettingType.Value>
        <PlanningCalendars.Value>Default</PlanningCalendars.Value>
        <SourceRule.Value>Default</SourceRule.Value>
    </Value>
    </Values>
</Rows>"""

def parsed = new XmlSlurper().parseText(xml)

def values = ['Rows' :  parsed.'**'.findAll{it.name() == 'Value'}.collect{element -> element.children().breadthFirst()*.name().findAll { !element."$it".children().size() }.collect{element."$it".text()}}]
def fields = ['Fields' :  parsed.'**'.find{it.name() == 'Value'}.collect{element -> element.children().breadthFirst()*.name()
             .findAll { !element."$it".children().size() }
           .collect{element."$it".name()}}]

fields += values
println new groovy.json.JsonBuilder(fields).toPrettyString()

I am getting below output:

{
    "Fields": [
        [
            "Name",
            "Site.Value",
            "Description",
            "Type.Value",
            "BuyerCode.Value",
            "PlannerCode.Value",
            "PlannerCode.Description",
            "ABCCode.Value",
            "UnitOfMeasure.Value",
            "SafetyStockQty",
            "StdUnitCost",
            "AverageSellingPrice",
            "ProductFamily.Value",
            "ProductGroup1",
            "IncrementalRule.Value",
            "MUEPoolNettingType.Value",
            "PlanningCalendars.Value",
            "SourceRule.Value"
        ]
    ],
    "Rows": [
        [
            "DummyTest_1",
            "3",
            "Dummy PN For Test 1",
            "N",
            "",
            "",
            "",
            "Default",
            "LB",
            "1",
            "10",
            "15",
            "",
            "CHC",
            "Default",
            "Ignore",
            "Default",
            "Default"
        ],
        [
            "DummyTest_2",
            "4",
            "Dummy PN For Test 2",
            "H",
            "",
            "",
            "",
            "Default2",
            "BL",
            "2",
            "20",
            "16",
            "",
            "CHC",
            "Default",
            "Ignore",
            "Default",
            "Default"
        ]
    ]
}

But the expected output is :

{
    "Fields": [
        [
            "Name",
            "Site.Value",
            "Description",
            "Type.Value",
            "BuyerCode.Value",
            "PlannerCode.Value",
            "PlannerCode.Description",
            "ABCCode.Value",
            "UnitOfMeasure.Value",
            "SafetyStockQty",
            "StdUnitCost",
            "AverageSellingPrice",
            "ProductFamily.Value",
            "ProductGroup1",
            "IncrementalRule.Value",
            "MUEPoolNettingType.Value",
            "PlanningCalendars.Value",
            "SourceRule.Value"
        ]
    ],
    "Rows": [
     {
        "Value":[
            "DummyTest_1",
            "3",
            "Dummy PN For Test 1",
            "N",
            "",
            "",
            "",
            "Default",
            "LB",
            "1",
            "10",
            "15",
            "",
            "CHC",
            "Default",
            "Ignore",
            "Default",
            "Default"
        ]
        },
        {
          "Value":[
            "DummyTest_2",
            "4",
            "Dummy PN For Test 2",
            "H",
            "",
            "",
            "",
            "Default2",
            "BL",
            "2",
            "20",
            "16",
            "",
            "CHC",
            "Default",
            "Ignore",
            "Default",
            "Default"
        ]
        }
    ]
}

I don't know how to populate "Values" Label before array like expected output.


Solution

  • You can transform the elements in values to Maps with Value as key. Try to change the line

    def values = ['Rows' :  parsed.'**'.findAll{it.name() == 'Value'}.collect{element -> element.children().breadthFirst()*.name().findAll { !element."$it".children().size() }.collect{element."$it".text()}}]
    

    to

    def values = ['Rows' :  parsed.'**'.findAll{it.name() == 'Value'}.collect{element -> [Value: element.children().breadthFirst()*.name().findAll { !element."$it".children().size() }.collect{element."$it".text()}]}]
    

    EDIT

    Now that I look at it again, the way you filter by !element."$it".children().size() may lead to unintended behaviour. I am not sure if that's what you really want. If you want all the leaves under Value you could try

    def values = ['Rows'  : parsed.'**'.findAll { it.name() == 'Value' }.collect { element -> [(element.name()): element.'**'.findAll { !it.childNodes() }*.text()] }]
    def fields = ['Fields': parsed.'**'.find    { it.name() == 'Value' }.collect { element ->                    element.'**'.findAll { !it.childNodes() }*.name()  }]
    

    EDIT2

    Just use collectMany instead of collect to flatten the inner list in fields by one level.

    def values = ['Rows'  : parsed.'**'.findAll { it.name() == 'Value' }.collect     { element -> [(element.name()): element.'**'.findAll { !it.childNodes() }*.text()] }]
    def fields = ['Fields': parsed.'**'.find    { it.name() == 'Value' }.collectMany { element ->                    element.'**'.findAll { !it.childNodes() }*.name()  }]