11

I'm converting a list of Foo objects to a JSON string. I need to parse the JSON string back into a list of Foos. However in the following example, parsing gives me a list of JSONObjects instead of Foos.

Example

List list = [new Foo("first"), new Foo("second")]
def jsonString = (list as JSON).toString()

List parsedList = JSON.parse(jsonString) as List
println parsedList[0].getClass() // org.codehaus.groovy.grails.web.json.JSONObject

How can I parse it into Foos instead? Thanks in advance.

4 Answers 4

13

I had a look at the API docs for JSON and there doesn't appear to be any way to parse to a JSON string to a specific type of object.

So you'll just have to write the code yourself to convert each JSONObject to a Foo. Something like this should work:

import grails.converters.JSON
import org.codehaus.groovy.grails.web.json.*

class Foo {
  def name

  Foo(name) {
    this.name = name
  }

  String toString() {
    name
  }
}


List list = [new Foo("first"), new Foo("second")]
def jsonString = (list as JSON).toString()

List parsedList = JSON.parse(jsonString)

// Convert from a list of JSONObject to a list of Foo
def foos = parsedList.collect {JSONObject jsonObject ->
    new Foo(name: jsonObject.get("name"))
}

A more general solution would be to add a new static parse method such as the following to the JSON metaClass, that tries to parse the JSON string to a List of objects of a particular type:

import grails.converters.JSON
import org.codehaus.groovy.grails.web.json.*

class Foo {
  def name

  Foo(name) {
    this.name = name
  }

  String toString() {
    name
  }
}

List list = [new Foo("first"), new Foo("second")]
def jsonString = (list as JSON).toString()


List parsedList = JSON.parse(jsonString)

// Define the new method
JSON.metaClass.static.parse = {String json, Class clazz ->

    List jsonObjs = JSON.parse(json)

    jsonObjs.collect {JSONObject jsonObj ->

        // If the user hasn't provided a targetClass read the 'class' proprerty in the JSON to figure out which type to convert to
        def targetClass = clazz ?: jsonObj.get('class') as Class
        def targetInstance = targetClass.newInstance()        

        // Set the properties of targetInstance
        jsonObj.entrySet().each {entry ->

            if (entry.key != "class") {
                targetInstance."$entry.key" = entry.value
            }
        }
        targetInstance
    }

}

// Try the new parse method
List<Foo> foos = JSON.parse(jsonString, Foo)

// Confirm it worked
assert foos.every {Foo foo -> foo.class == Foo && foo.name in ['first', 'second'] }

You can try out the code above in the groovy console. A few warnings

  • I have only performed very limited testing on the code above
  • There are two JSON classes in the latest Grails release, I'm assuming you're using the one that is not deprecated
Sign up to request clarification or add additional context in comments.

5 Comments

Would it be possible to inject the attributes of each jsonObj directoy into the foo.properties field for each new instance of Foo?
@Ali G - No, I think .properties is only writeable for Grails domain objects. For regular Groovy objects, I think .properties is read-only.
Thanks Don. The generic approach is very nice.
Fair point, Don - I was making some big assumptions there, I guess!
How do you deal with dates and nullable values?
4

If you are doing this in a Grails controller, and Foo IS indeed a domain object, don't forget that armed with your JSON map, you can also do:

List list = [new Foo("first"), new Foo("second")]
def jsonString = (list as JSON).toString()

List parsedList = JSON.parse(jsonString) as List
Foo foo = new Foo()
bindData(foo, parsedList[0]);

1 Comment

or even better Foo foo = new Foo(parsedList[0])
4

I've taken this code and extended it to work with nested structures. It relies on a 'class' attribute existing in the JSON. If there's a better way by now in Grails please let me know.

     // The default JSON parser just creates generic JSON objects.  If there are nested

    // JSON arrays they are not converted to theirs types but are left as JSON objects
    // This converts nested JSON structures into their types.
    // IT RELIES ON A PROPERTY 'class' that must exist in the JSON tags
    JSON.metaClass.static.parseJSONToTyped = {def jsonObjects ->

        def typedObjects = jsonObjects.collect {JSONObject jsonObject ->
            if(!jsonObject.has("class")){
                throw new Exception("JSON parsing failed due to the 'class' attribute missing: " + jsonObject)
            }

            def targetClass = grailsApplication.classLoader.loadClass(jsonObject.get("class"))
            def targetInstance = targetClass.newInstance()

            // Set the properties of targetInstance
            jsonObject.entrySet().each {entry ->
                // If the entry is an array then recurse
                if(entry.value instanceof org.codehaus.groovy.grails.web.json.JSONArray){
                    def typedSubObjects = parseJSONToTyped(entry.value)
                    targetInstance."$entry.key" = typedSubObjects
                }
                else if (entry.key != "class") {
                    targetInstance."$entry.key" = entry.value
                }
            }

            targetInstance
        }

        return typedObjects
    }

Comments

0

As of Grails 2.5, this is possible:

Period test = new Period()
test.periodText = 'test'
String j = test as JSON
def p = JSON.parse(j)
test = p.asType(Period)
println(test.periodText)

Output:

test

I am unsure of when it became an option.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.