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.