I'm trying to marshal and unmarshal a domain (e.g. OrgUnit) instance as JSON to a database field like this:
class OrgUnit{
String name
OrgUnit parent
static hasMany = [children:orgUnit]
}
class History{
String data
}
class OrgUnitService{
History marshal(OrgUnit orgUnit){
return new History([
data : (orgUnit.properties as JSON).toString()
]).save()
}
OrgUnit unmarshal(History history){
return OrgUnit.newInstance( JSON.parse(history.data))
}
}
It works fine for simple fields like name, but fields like children are empty in the unmarshaled object.
The history.data field contains children information like this:
{"name":"b","children":[{"class":"demo.OrgUnit","id":3,"children":null,"name":"c"}]}
I'm using Grails 2.2.4. Any suggestions !?
Update I tested it on Grails 2.4.3. It works as expected. The content of the history.data field is in both Grails versions identical. The issue is in the unmarshaling part.
The critical part is the creation of a domain instance by properties map. It is used in the default save action in controllers. It might be a security issue in controller, but I take it out of scope there.
In Grails version 2.2.4 it doesn't bind the referencies to the instance. The workaround there is, to do it manually like this:
class HistoryService {
def grailsApplication
History marshal(OrgUnit orgUnit) {
return new History([
data: (orgUnit.properties as JSON).toString()
]).save(failOnError: true)
}
OrgUnit unmarshal(History history) {
def data = JSON.parse(history.data)
OrgUnit instance = OrgUnit.newInstance(data)
def domainClass = grailsApplication.getDomainClass(OrgUnit.class.name)
domainClass.persistentProperties.each{p->
if (p.oneToMany || p.manyToMany){
def refDataList = data."$p.name"
refDataList.each{refData->
instance."$p.name" << getDomainClass(refData.class).read(refData.id as Long)
}
}else if (p.manyToOne || p.oneToOne){
def refData = data."$p.name"
if(refData && refData.class && refData.id){ // if reference field has value null
instance."$p.name" = getDomainClass(refData.class).read(refData.id as Long)
}
}
}
return instance
}
private def getDomainClass(String domainName){
return grailsApplication.classLoader.loadClass(domainName)
}
}