Skip to content

Instantly share code, notes, and snippets.

@double16
Last active August 29, 2015 14:27
Show Gist options
  • Save double16/ba92843a0fc66836bb28 to your computer and use it in GitHub Desktop.
Save double16/ba92843a0fc66836bb28 to your computer and use it in GitHub Desktop.
Adapt Gson for Grails Object Marshaling
[JSON, XML]*.registerObjectMarshaller(DateTime) {
return it?.toString("yyyy-MM-dd'T'HH:mm:ss'Z'")
}
import grails.converters.JSON
import grails.converters.XML
import org.bson.types.ObjectId
import org.springframework.context.MessageSourceResolvable
import org.springframework.context.i18n.LocaleContextHolder
class BootStrap {
def init = { servletContext ->
[JSON, XML /*Gson already handles ObjectID*/]*.registerObjectMarshaller(ObjectId) {
return it?.toString()
}
[JSON, XML, GSONFAC]*.registerObjectMarshaller(MessageSourceResolvable) {
return it ? messageSource.getMessage(it, LocaleContextHolder.locale) : null
}
[JSON, XML, GSONFAC]*.registerObjectMarshaller(Enum) {
it?.name()
}
}
}
import com.google.gson.Gson
import com.google.gson.TypeAdapter
import com.google.gson.TypeAdapterFactory
import com.google.gson.reflect.TypeToken
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonToken
import com.google.gson.stream.JsonWriter
/**
* Allows closures to be used to marshall objects when {@link Gson} is used for serialization.
*/
class ClosureTypeAdapterFactory implements TypeAdapterFactory {
private Map<Class<?>, Closure<?>> converters = [:]
void registerObjectMarshaller(Class<?> clazz, Closure<?> callable) {
converters[clazz] = callable
}
@SuppressWarnings('UnusedMethodParameter')
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
Class<T> rawType = (Class<T>) type.getRawType();
Closure<?> callable = converters[rawType]
if (!callable) {
return null;
}
return new TypeAdapter() {
@SuppressWarnings("CatchException")
public void write(JsonWriter out, def value) throws IOException {
if (value == null) {
out.nullValue();
} else {
try {
int argCount = callable.getParameterTypes().length;
Object result;
if (argCount == 1) {
result = callable.call(value);
}
else {
throw new IOException(
"Invalid Parameter count for registered Object Marshaller for class " + rawType.getName());
}
if (result == null) {
out.nullValue()
} else {
out.value(result)
}
}
catch (Exception e) {
throw e instanceof IOException ? (IOException) e : new IOException(e);
}
}
}
public def read(JsonReader reader) throws IOException {
if (reader.peek() == JsonToken.NULL) {
reader.nextNull()
return null
} else {
reader.nextString()
return null
}
}
}
}
}
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonToken
import com.google.gson.stream.JsonWriter
import org.springframework.context.MessageSource
import org.springframework.context.MessageSourceResolvable
import org.springframework.context.i18n.LocaleContextHolder
import org.springframework.context.support.DefaultMessageSourceResolvable
import spock.lang.Specification
class ClosureTypeAdapterFactorySpec extends Specification {
public static final MessageSourceResolvable MESSAGE = new DefaultMessageSourceResolvable("default.boolean.true")
ClosureTypeAdapterFactory factory
Gson gson
TypeToken<List<String>> listStringType
TypeToken<MessageSourceResolvable> messageSourceResolvableType
StringWriter stringWriter
JsonWriter jsonWriter
def setup() {
factory = new ClosureTypeAdapterFactory()
def messageSource = Stub(MessageSource)
messageSource.getMessage(_, _) >> "resolved message"
factory.registerObjectMarshaller(MessageSourceResolvable) {
return messageSource.getMessage(it, LocaleContextHolder.locale)
}
def factoryBean = new GsonFactoryBean()
factoryBean.closureTypeAdapterFactory = factory
factoryBean.afterPropertiesSet()
gson = factoryBean.object
listStringType = new TypeToken<List<String>>() {}
messageSourceResolvableType = new TypeToken<MessageSourceResolvable>() {}
stringWriter = new StringWriter()
jsonWriter = new JsonWriter(stringWriter)
jsonWriter.beginArray()
}
def "handle unsupported type"() {
expect:
!factory.create(gson, listStringType)
}
def "handle supported type"() {
expect:
factory.create(gson, messageSourceResolvableType)
}
def "write null value"() {
given:
def typeAdapter = factory.create(gson, messageSourceResolvableType)
when:
typeAdapter.write(jsonWriter, null)
then:
stringWriter.toString() == "[null"
}
def "write value"() {
given:
def typeAdapter = factory.create(gson, messageSourceResolvableType)
when:
typeAdapter.write(jsonWriter, MESSAGE)
then:
stringWriter.toString() == "[\"resolved message\""
}
def "write value with null conversion"() {
given:
factory.registerObjectMarshaller(MessageSourceResolvable) {
return null
}
def typeAdapter = factory.create(gson, messageSourceResolvableType)
when:
typeAdapter.write(jsonWriter, MESSAGE)
then:
stringWriter.toString() == "[null"
}
def "write value with exception"() {
given:
factory.registerObjectMarshaller(MessageSourceResolvable) {
throw new UnsupportedOperationException()
}
def typeAdapter = factory.create(gson, messageSourceResolvableType)
when:
typeAdapter.write(jsonWriter, MESSAGE)
then:
thrown(IOException)
}
def "read null"() {
given:
def typeAdapter = factory.create(gson, messageSourceResolvableType)
def jsonReader = Stub(JsonReader) {
peek() >> JsonToken.NULL
}
expect:
typeAdapter.read(jsonReader) == null
}
def "read string"() {
given:
def typeAdapter = factory.create(gson, messageSourceResolvableType)
def jsonReader = Stub(JsonReader) {
peek() >> JsonToken.STRING
nextString() >> "resolved message"
}
expect:
typeAdapter.read(jsonReader) == null
}
def "zero arg closure"() {
given:
factory.registerObjectMarshaller(MessageSourceResolvable) { ->
"zero arg"
}
when:
gson.getAdapter(MessageSourceResolvable).toJson(MESSAGE)
then:
thrown(IOException)
}
def "two arg closure"() {
given:
factory.registerObjectMarshaller(MessageSourceResolvable) { one, two ->
"two arg"
}
when:
gson.getAdapter(MessageSourceResolvable).toJson(MESSAGE)
then:
thrown(IOException)
}
}
beans = {
GSONFAC(ClosureTypeAdapterFactory)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment