Search code examples
jsongrailsgrails-ormmarshallingunmarshalling

Marshal / unmarshal domain instance to JSON in Grails


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.


Solution

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