Better serialization of Groovy objects using XStream

This blog post is to resolve a little disconnect between Groovy and XStream serialization library:

  • Groovy makes use of synthetic members quite a bit, and sometimes it also adds synthetic fields to classes it compiles.
  • XStream skips only static and transient data members (as of the latest version 1.4.1), but not synthetic ones, resulting in a little inconvenience that Groovy’s compiler-provided synthetic members also show up in serialized form.

Here is an example, where we try to serialize a Groovy object into JSON format:

import groovy.transform.Immutable
import com.thoughtworks.xstream.XStream
import com.thoughtworks.xstream.io.json.JsonHierarchicalStreamDriver

@Grab(group='com.thoughtworks.xstream', module='xstream', version='1.4.1')
XStream xstream = new XStream(new JsonHierarchicalStreamDriver())

def person = new Person(firstName: 'roshan', lastName: 'dawrani')
println xstream.toXML(person)

@Immutable
class Person {
	String firstName
	String lastName
}

The above code outputs the following because AST transformation done by compiler for @Immutable adds some extra fields for its internal use, such as “$print$names” and “$hash$code”, and it can be a little confusing or annoying to see these unknown data members mixed up with your regular ones.

{"Person": {
  "firstName": "roshan",
  "lastName": "dawrani",
  "$print$names": true,
  "$hash$code": 0
}}

In the following code, we try to remove the disconnect between Groovy and XStream by introducing a custom converter that enhances the filter applied by XStream on data members and excludes the synthetic ones as well:

import groovy.transform.Immutable

import java.lang.reflect.Field
import java.lang.reflect.Modifier

import com.thoughtworks.xstream.XStream
import com.thoughtworks.xstream.io.json.JsonHierarchicalStreamDriver
import com.thoughtworks.xstream.converters.reflection.PureJavaReflectionProvider
import com.thoughtworks.xstream.converters.reflection.ReflectionConverter
import com.thoughtworks.xstream.mapper.Mapper

@Grab(group='com.thoughtworks.xstream', module='xstream', version='1.4.1')
XStream xstream = new XStream(new JsonHierarchicalStreamDriver())
xstream.registerConverter(new GroovyObjectConverter(xstream.mapper))

def person = new Person(firstName: 'roshan', lastName: 'dawrani')
println xstream.toXML(person)

@Immutable
class Person {
    String firstName
    String lastName
}

class GroovyObjectConverter extends ReflectionConverter {
    GroovyObjectConverter(Mapper mapper) {
        super(mapper, new GroovyObjectReflectionProvider())
    }

    boolean canConvert(Class type) {
        GroovyObject.class.isAssignableFrom(type)
    }

}

class GroovyObjectReflectionProvider extends PureJavaReflectionProvider {
    protected boolean fieldModifiersSupported(Field field) {
        int modifiers = field.getModifiers()
        super.fieldModifiersSupported(field) && !Modifier.isSynthetic(modifiers)
    }
}

With this technique the output is correctly shown as:

{"Person": {
  "firstName": "roshan",
  "lastName": "dawrani"
}}

Hope the technique is useful to some of you.

Advertisements