1

I want to teach my domain class to automatically convert the results of JSON.parse(someJSON) into a member that is also a custom domain class.

Given these domain classes:

class Person {
    Long id
    String name

    static hasMany = [aliases: PersonAlias]
}

class PersonAlias {
    Person person
    Long id
    String name
}

And this JSON representing a Person with some PersonAliases:

{
     "id":20044397,
     "name":"John Smith",
     "aliases":[{"id":13376,"name":"Johnny Smith"},{"id":13377,"name":"J. Smith"}]
}

I want to keep the controller simple like:

class PersonController {
    def saveViaAjax = {
        def props = JSON.parse(params.JSON)
        Person p = Person.get(props.id)
        p.properties = props
        p.save(flush: true)
    }
}

But sadly I get this error:

Failed to convert property value of type 'org.codehaus.groovy.grails.web.json.JSONArray' to required type 'java.util.Set' for property 'aliases'; nested exception is java.lang.IllegalStateException: Cannot convert value of type [org.codehaus.groovy.grails.web.json.JSONObject] to required type [heavymeta.PersonAlias] for property 'aliases[0]': no matching editors or conversion strategy found

So, I want to teach my domain class to how to convert the JSON data into PersonAlias instances automatically. I'd like to avoid formatting the data in the controller before passing it to the Domain object. How do I accomplish these goals?

3
  • Grails should be able to marshal JSON already. There's a setting that needs to be switched on in config.groovy. Commented Aug 7, 2012 at 12:59
  • Grails does already marshall JSON... to a certain extent. In the code above, I get a viable Person object except for the member aliases Commented Aug 7, 2012 at 21:49
  • did you find a solution for your question? Commented Feb 26, 2013 at 6:53

3 Answers 3

1

You can use the bindUsing annotation and provide your custom binding code to convert the json to the property being bound.

class Person {
    Long id
    String name

    @BindUsing({obj, source ->
       List retVal = [] 
       def aliases = source['aliases']
       if(aliases) {
           aliases.each { 
             retVal << new PersonAlias(name:it.name)
           }
       } 
       return retVal
    })
    List<PersonAlias> aliases

    static hasMany = [aliases: PersonAlias]
}
Sign up to request clarification or add additional context in comments.

Comments

0

I think this plugin: https://github.com/pedjak/grails-marshallers might do what you're looking for? I have not tried it myself though.

1 Comment

I like that plugin, but it doesn't provide the functionality I'm asking for. It provides an alternative way to leverage Grails' existing facility to marshal an object into JSON. I need to convert JSON into instances of my domain classes. I have code to do so, but I want to plug it into my domain class so that it simply happens as a side effect of domainInstance.properties = convertedJSON.
0

I also encountered this problem - I did my best to document the fix on my website - See http://dalelotts.com/software-architect/grails

In general the solution is to convert the JSON to a parameter map that can be used for data binding. More info on the site, including an annotation driven DomainClassMarshaller for JSON

protected Object readFromJson(Class type, InputStream entityStream, String charset) {

   def mapper = new ObjectMapper();
   def parsedJSON = mapper.readValue(entityStream, typeRef);

   Map<String, Object> map = new HashMap<>();

   parsedJSON.entrySet().each {Map.Entry<String, Object> entry ->
       if (List.isAssignableFrom(entry.getValue().getClass())) {

           List values = (List) entry.getValue();
           int limit = values.size()
           for (int i = 0; i <  limit; i++) {
               final theValue = values.get(i)
               map.put(entry.key + '[' + i + ']', theValue)

               appendMapValues(map, theValue, entry.key + '[' + i + ']' )

           }
       } else {
           map.put(entry.key, entry.value);
       }
   }

   def result = type.metaClass.invokeConstructor(map)


   // Workaround for http://jira.codehaus.org/browse/GRAILS-1984
   if (!result.id) {
       result.id = idFromMap(map)
   }
   result
}

private void appendMapValues(Map<String, Object> theMap, Object theValue, String prefix) {
    if (Map.isAssignableFrom(theValue.getClass())) {

        Map<String, Object> valueMap = (Map<String, Object>) theValue;

        for (Map.Entry<String, Object> valueEntry : valueMap.entrySet()) {
            theMap.put(prefix + '.' + valueEntry.key, valueEntry.value)
            appendMapValues(theMap, valueEntry.value, prefix + '.' + valueEntry.key)
        }
    }
}

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.