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?

5 comments:

ziegfried said...

hi

the priority stuff is already fixed and will be in the next release of the Grails 1.2 branch. Regarding the ByteArrayMarshaller stuff, you might want to raise a JIRA issue (against the converters component) and I'll take a look at it asap.

Cheers, sigi

foxgem said...

great! this stuff is really annoying. I just raised a jira issue about ByteArarrayMarshaller: http://jira.codehaus.org/browse/GRAILS-5143. thanks, sigi.

Anonymous said...

java.lang.NoSuchMethodError: org.codehaus.groovy.grails.web.converters.marshaller.json.DomainClassMarshaller: method ()V not found

Do you know why?

Tarun said...

Hi,

I'm facing a strange issue in Grails 2.1.2. I registered a custom date marshaller for JSON converter. After my application starts within some time I see weird thing happens that at time Grails uses Default date marshaller and at times the custom date marshaller I registered.

I saw the priority note mentioned by you. Could it be related to that? I haven't used priority in my code

Unknown said...

Thanks for pointing out the priority of registered marshallers. My problem was somehow different:

I have a domain class which inherits from another class. Both classes define properties and have their own JSON marshallers:

class A{
static marshalling={
JSON.registerObjectMarshaller(A) { A a ->
return [
p1: a.p1,
p2: a.p2
]
}
}
String p1
String p2
}

class B extends A{
static marshalling={
JSON.registerObjectMarshaller(B) { B b ->
return [
p1: b.p1,
p3: b.p3 ]
}
}
String p3
}


In my controller I did something like

..
render B.list() as JSON
..

but it always used the JSON marshaller definition of class A! So if you have a problem with inheritance and JSON marshaller (using extends in a class and custom JSON definition and inherit from a super class) you should use priorities!

So in my example I fixed the code to

JSON.registerObjectMarshaller(B,2) { B b ->
return [
p1: b.p1,
p3: b.p3
]
}

and everything is fine. Took me a few hours of my life-time, maybe some will save this time by reading about my grails JSON converter inheritance problem.

Best greetings