Scott Ingram
Scott Ingram

Reputation: 85

Grails - how to let a domain class convert JSON into a domain property

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?

Upvotes: 1

Views: 4698

Answers (3)

Sudhir N
Sudhir N

Reputation: 4096

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]
}

Upvotes: 1

dale.lotts
dale.lotts

Reputation: 419

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)
        }
    }
}

Upvotes: 0

sbglasius
sbglasius

Reputation: 3124

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

Upvotes: 0

Related Questions