Search code examples
groovyclosuresjsonbuilder

Caught: java.lang.StackOverflowError JsonBuilder closure


I've been trying to read an xml file and convert it to json using groovy's JsonBuilder. The problem is that when I print with

def builder = new JsonBuilder(jsonObject)
println builder.toPrettyString()

I got a Caught: java.lang.StackOverflowError

Here is the whole stacktrace

Exception in thread "main" java.lang.StackOverflowError
    at groovy.json.JsonOutput.writeObject(JsonOutput.java:259)
    at groovy.json.JsonOutput.writeIterator(JsonOutput.java:442)
    at groovy.json.JsonOutput.writeObject(JsonOutput.java:272)
    at groovy.json.JsonOutput.writeIterator(JsonOutput.java:442)
    at groovy.json.JsonOutput.writeObject(JsonOutput.java:272)
    at groovy.json.JsonOutput.writeIterator(JsonOutput.java:442)
    at groovy.json.JsonOutput.writeObject(JsonOutput.java:272)
    at groovy.json.JsonOutput.writeIterator(JsonOutput.java:442)
    at groovy.json.JsonOutput.writeObject(JsonOutput.java:272)
    at groovy.json.JsonOutput.writeIterator(JsonOutput.java:442)
    at groovy.json.JsonOutput.writeObject(JsonOutput.java:272)
    at groovy.json.JsonOutput.writeIterator(JsonOutput.java:442)
    at groovy.json.JsonOutput.writeObject(JsonOutput.java:272)
    at groovy.json.JsonOutput.writeIterator(JsonOutput.java:442)

Here the code.

package firstgroovyproject

import groovy.json.JsonBuilder

class XmlToJsonII {
    static void main(def args){

        def carRecords = '''

<records>
    <car name='HSV Maloo' make='Holden' year='2006'>
        <countries>
            <country>
                Austria
            </country>
            <country>
                Spain
            </country>
        </countries>
        <record type='speed'>Production Pickup Truck with speed of 271kph
        </record>
    </car>
    <car name='P50' make='Peel' year='1962'>
        <countries>
            <country>
                Monaco
            </country>
        </countries>
        <record type='size'>Smallest Street-Legal Car at 99cm wide and 59 kg
            in weight</record>
    </car>
    <car name='Royale' make='Bugatti' year='1931'>
        <record type='price'>Most Valuable Car at $15 million</record>
        <countries>
            <country>
                Italia
            </country>
        </countries>
    </car>
<car name='Seat' make='Ibiza' year='1985'>
        <record type='price'>barato</record>
        <countries>
            <country>
                Spain
            </country>
        </countries>
    </car>
</records>
  '''


        def xmlRecords = new XmlSlurper().parseText(carRecords)

        def jsonObject = [:]
        jsonObject.records = []
        def records = jsonObject.records



        xmlRecords.car.each {xmlCar ->  
            records.add([
                countries:
                xmlCar.countries.children().each{ country ->
                        println   "country : ${country.text()}"
                        [country:   country.text()]
                },

                ])
        }

        def builder = new JsonBuilder(jsonObject)

        println builder.toPrettyString()
        //println builder.toString()
    }
}

Solution

  • tl;dr: Your second (inner) each should be a collect instead.

    The real answer: The return value of each is the original Iterable that invoked it. In this case that would be the XML object collection defined by the expression xmlCar.countries.children(). Since the objects in that collection contain references back to their own parents your JSON build causes an infinite regress that results in a stack overflow.

    This doesn't happen with your first (outer) use of each, because you are not using the return value. Instead you are adding to a preexisting list (records).

    By changing the second (inner) each into a collect, you are still iterating through the children of the countries elements. However, instead of returning the original XML children (the result of each) you are compiling and returning a list of maps of the form [country:"country_string_from_xml"], which seems to be the desired behavior.

    Confusing each and collect is a common rookie mistake in Groovy... which only made it all the more galling when it happened to ME last week. :-)