Reputation: 85
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
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
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
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