Thursday, September 24, 2009

More about Object Marshaller

Should I need to know something about Object Marshaller since Grails has done such good job in this aspect? Most of the time, the answer is not. But if you are doing something about RIA or Ajax, the answer is yes.
Why? The reason is maybe:
1. You want to include some properties in JSON output which was not included in default, such as version. By the way, you can include version through three config option in grails new version. You can find more details on this issue at this or my last post.
2. You like to format some property yourself, such as date formatting
3. You want to output a object into a new JSON string, such as I want to output a relation property's toString() not its id.
4. ...
These days I was doing a RIA application using grails and ext. And, I met all the three requirements I said above. And here is what I got during this project:
1. To implement your marshaller, you can implement ObjectMarshaller or you can use closure when you register it. If you want to use closure, you can do this:

JSON.registerObjectMarshaller(Locale.class){
def rval= [:]
rval['ID']= it.toString()
rval['displayName']= it.getDisplayName()
return rval
}

For method 1, implementing ObjectMarshaller, You can check DomainClassMarshaller.java. But if what you want is just to output the return value of toString() of relation property, you really don't have to do it from the ground up. You can extend DomainClassMarshaller:

class AnotherDomainClassMarshaller extends DomainClassMarshaller{
protected void asShortObject(Object refObj, JSON json,
GrailsDomainClassProperty idProperty,
GrailsDomainClass referencedDomainClass) throws ConverterException {
JSONWriter writer = json.getWriter();
writer.object();
writer.key("class").value(referencedDomainClass.getName());
writer.key("id").value(extractValue(refObj, idProperty));
writer.key("value").value(refObj.toString());
writer.endObject();
}
}

2. After you create your marshaller, you should register it. And the best place in grails application is BootStrap.groovy:
JSON.registerObjectMarshaller(your marshaller instance)

3. If you want to register more than one marshaller, you should provide a priority. REMEMBER IT! Yes, it is a trick. If you ignore it, you will get a surprise: Your marshaller registered just now did not work! After I digged the source of grails, DefaultConverterConfiguration.java, I found the truth: Grails hold all the object marshallers in a treeset, which use priority as a comparator, if you register two marshallers with the same priority, grails will register your marshaller with default-priority(=0) if priority is missing, the second marshaller will not be registered. So my last version of BootStrap.groovy is:

JSON.registerObjectMarshaller(...)
JSON.registerObjectMarshaller(..., 1)
JSON.registerObjectMarshaller(..., 2)
But there is a problem I don't understand: the default ByteArrayMarshaller did not work, and I have to register it myself and provide a priority like this:

JSON.registerObjectMarshaller(new org.codehaus.groovy.grails.web.converters.marshaller.json.ByteArrayMarshaller(), 1)
Who can tell me why?