Serializing and Deserializing Lambda with Jackson - json

I am trying to serialise and deserialise a class RuleMessage but can't get it to work. Here is my code:
public class RuleMessage {
private String id;
private SerializableRunnable sRunnable;
public RuleMessage(String id, SerializableRunnable sRunnable) {
this.id = id;
this.sRunnable = sRunnable;
}
}
public interface SerializableRunnable extends Runnable, Serializable {
}
#Test
public void testSerialization() throws JsonProcessingException {
MAPPER.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL,
JsonTypeInfo.As.PROPERTY);
SerializableRunnable r = () -> System.out.println("Serializable!");
RuleMessage rule = new RuleMessage("1", r);
System.out.println(MAPPER.writeValueAsString(businessRule));
}
I am using Java 8. Can someone tell me if this is possible in the Jackson library?

Jackson was created to keep object state not behaviour. This is why it tries to serialise POJO's properties using getters, setters, etc. Serialising lambdas break this idea. Theres is no any property to serialise, only a method which should be invoked. Serialising raw lambda object is really bad idea and you should redesign your app to avoid uses cases like this.
In your case SerializableRunnable interface extends java.io.Serializable which gives one option - Java Serialisation. Using java.io.ObjectOutputStream we can serialise lambda object to byte array and serialise it in JSON payload using Base64 encoding. Jackson supports this scenario providing writeBinary and getBinaryValue methods.
Simple example could look like below:
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class JsonLambdaApp {
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
SerializableRunnable action = () -> System.out.println("Serializable!");
String json = mapper.writeValueAsString(new RuleMessage("1", action));
System.out.println(json);
RuleMessage ruleMessage = mapper.readValue(json, RuleMessage.class);
ruleMessage.getsRunnable().run();
}
}
#JsonSerialize(using = LambdaJsonSerializer.class)
#JsonDeserialize(using = LambdaJsonDeserializer.class)
interface SerializableRunnable extends Runnable, Serializable {
}
class LambdaJsonSerializer extends JsonSerializer<SerializableRunnable> {
#Override
public void serialize(SerializableRunnable value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream outputStream = new ObjectOutputStream(byteArrayOutputStream)) {
outputStream.writeObject(value);
gen.writeBinary(byteArrayOutputStream.toByteArray());
}
}
}
class LambdaJsonDeserializer extends JsonDeserializer<SerializableRunnable> {
#Override
public SerializableRunnable deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
byte[] value = p.getBinaryValue();
try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(value);
ObjectInputStream inputStream = new ObjectInputStream(byteArrayInputStream)) {
return (SerializableRunnable) inputStream.readObject();
} catch (ClassNotFoundException e) {
throw new IOException(e);
}
}
}
class RuleMessage {
private String id;
private SerializableRunnable sRunnable;
#JsonCreator
public RuleMessage(#JsonProperty("id") String id, #JsonProperty("sRunnable") SerializableRunnable sRunnable) {
this.id = id;
this.sRunnable = sRunnable;
}
public String getId() {
return id;
}
public SerializableRunnable getsRunnable() {
return sRunnable;
}
}
Above code prints JSON:
{
"id" : "1",
"sRunnable" : "rO0ABXNyACFqYXZhLmxhbmcuaW52b2tlLlNlcmlhbGl6ZWRMYW1iZGFvYdCULCk2hQIACkkADmltcGxNZXRob2RLaW5kWwAMY2FwdHVyZWRBcmdzdAATW0xqYXZhL2xhbmcvT2JqZWN0O0wADmNhcHR1cmluZ0NsYXNzdAARTGphdmEvbGFuZy9DbGFzcztMABhmdW5jdGlvbmFsSW50ZXJmYWNlQ2xhc3N0ABJMamF2YS9sYW5nL1N0cmluZztMAB1mdW5jdGlvbmFsSW50ZXJmYWNlTWV0aG9kTmFtZXEAfgADTAAiZnVuY3Rpb25hbEludGVyZmFjZU1ldGhvZFNpZ25hdHVyZXEAfgADTAAJaW1wbENsYXNzcQB+AANMAA5pbXBsTWV0aG9kTmFtZXEAfgADTAATaW1wbE1ldGhvZFNpZ25hdHVyZXEAfgADTAAWaW5zdGFudGlhdGVkTWV0aG9kVHlwZXEAfgADeHAAAAAGdXIAE1tMamF2YS5sYW5nLk9iamVjdDuQzlifEHMpbAIAAHhwAAAAAHZyABxjb20uY2Vsb3hpdHkuSnNvblR5cGVJbmZvQXBwAAAAAAAAAAAAAAB4cHQAIWNvbS9jZWxveGl0eS9TZXJpYWxpemFibGVSdW5uYWJsZXQAA3J1bnQAAygpVnQAHGNvbS9jZWxveGl0eS9Kc29uVHlwZUluZm9BcHB0ABZsYW1iZGEkbWFpbiQ1YzRiNmEwOCQxcQB+AAtxAH4ACw=="
}
and lambda:
Serializable!
See also:
How to serialize a lambda?
How to serialize a lambda function in Java?

First, in RuleMessage you have to either create getters / setters or make the fields public in order to provide Jackson access to the fields.
Your code then prints something like this:
{"#class":"RuleMessage","id":"1","sRunnable":{"#class":"RuleMessage$$Lambda$20/0x0000000800b91c40"}}
This JSON document cannot be deserialized because RuleMessage has no default constructor and the lambda cannot be constructed.
Instead of the lambda, you could create a class:
public class Runner implements SerializableRunnable {
#Override
public void run() {
System.out.println("Serializable!");
}
}
and construct your pojo like this:
new RuleMessage("1", new Runner())
The Jackson deserializer is now able to reconstruct the objects and execute the runner.

Related

Is there way to deserialise {"number1":5L,"number2":5L} to a class having long fields?

When I deserialise JSON:
{"number1":5L,"number2":5L}
to a class having long fields, I get below error:
JsonParseException: Unexpected character ('L' ): was expecting comma
to separate Object entries
How to fix it?
JSON payload is not valid. Number can not be represented with letter L at the end. See below chart:
Above picture comes from json.org. To handle invalid JSON we need to implement custom deserialiser for Long class:
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.deser.std.NumberDeserializers;
import java.io.File;
import java.io.IOException;
import java.util.StringJoiner;
public class JsonPathApp {
public static void main(String[] args) throws Exception {
File jsonFile = new File("./resource/test.json").getAbsoluteFile();
ObjectMapper mapper = new ObjectMapper();
Id id = mapper.readValue(jsonFile, Id.class);
System.out.println(id);
}
}
class LongJsonDeserializer extends JsonDeserializer<Long> {
private final NumberDeserializers.LongDeserializer longDeserializer = new NumberDeserializers.LongDeserializer(Long.TYPE, 0L);
#Override
public Long deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
Long value = longDeserializer.deserialize(p, ctxt);
goToNextTokenSilently(p);
return value;
}
private void goToNextTokenSilently(JsonParser p) {
try {
p.nextToken();
} catch (Exception e) {
//log if needed
}
}
}
class Id {
#JsonDeserialize(using = LongJsonDeserializer.class)
private Long number1;
#JsonDeserialize(using = LongJsonDeserializer.class)
private Long number2;
// getters, setters, toString
}

Spring Boot adding attribute to XML element but NOT in JSON response

I am working on an API that produces both XML and JSON responses. I have one element in the response which requires an attribute only in XML response. Also, when the value is null, the element shouldn't show up in the response for both formats.
Expectation:
XML:
<name>john</name>
<status type="text">married</status>
JSON:
"name":"john"
"status":"married"
This is my code:
/**
* POJO with bunch of LOMBOK annotations to avoid boiler-plate code.
*/
#AllArgsConstructor
#NoArgsConstructor
#Builder(toBuilder = true)
#Data
public class User implements Customer, Serializable {
private static final long serialVersionUID = 1L;
private Status status;
private String name;
/**
* Matrital status of the user.
*/
#Builder
#Value
public static class Status {
#JacksonXmlText
private String maritalStatus;
#JacksonXmlProperty(isAttribute = true)
private String type = "text";
}
}
With the above change, I am getting the correct XML response but JSON response also returns type=text
"status" : {
"maritalStatus" : "married",
"type" : "text"
}
I tried to add #JsonValue to private String maritalStatus, that solved the JSON response but it broke XML response by not adding the attribute to the element.
Can someone please help?
Probably the easiest way is to implement custom serialiser for User.Status and produce different output for different kinds of representation.
class UserStatusJsonSerializer extends JsonSerializer<User.Status> {
#Override
public void serialize(User.Status value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
if (gen instanceof ToXmlGenerator) {
ToXmlGenerator toXmlGenerator = (ToXmlGenerator) gen;
serializeXml(value, toXmlGenerator);
} else {
gen.writeString(value.getMaritalStatus());
}
}
private void serializeXml(User.Status value, ToXmlGenerator toXmlGenerator) throws IOException {
toXmlGenerator.writeStartObject();
toXmlGenerator.setNextIsAttribute(true);
toXmlGenerator.writeFieldName("type");
toXmlGenerator.writeString(value.getType());
toXmlGenerator.setNextIsAttribute(false);
toXmlGenerator.writeRaw(value.getMaritalStatus());
toXmlGenerator.writeEndObject();
}
#Override
public boolean isEmpty(SerializerProvider provider, User.Status value) {
return value == null || value.getMaritalStatus() == null;
}
}
Since now, you can remove extra XML annotations and register custom serialiser:
#AllArgsConstructor
#NoArgsConstructor
#Builder(toBuilder = true)
#Data
class User implements Serializable {
private static final long serialVersionUID = 1L;
private Status status;
private String name;
#Builder
#Value
#JsonSerialize(using = UserStatusJsonSerializer.class)
public static class Status {
private String maritalStatus;
private String type = "text";
}
}
Simple console app usage could look like below:
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.Value;
import java.io.IOException;
import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
public class JsonPathApp {
public static void main(String[] args) throws Exception {
List<User> users = Arrays.asList(
createUser("John", "married"),
createUser("Tom", null));
ObjectMapper jsonMapper = JsonMapper.builder()
.enable(SerializationFeature.INDENT_OUTPUT)
.serializationInclusion(JsonInclude.Include.NON_EMPTY)
.build();
for (User user : users) {
System.out.println(jsonMapper.writeValueAsString(user));
System.out.println();
}
XmlMapper xmlMapper = XmlMapper.builder()
.enable(SerializationFeature.INDENT_OUTPUT)
.serializationInclusion(JsonInclude.Include.NON_EMPTY)
.build();
for (User user : users) {
System.out.println(xmlMapper.writeValueAsString(user));
System.out.println();
}
}
private static User createUser(String name, String maritalStatus) {
return User.builder()
.name(name)
.status(User.Status.builder()
.maritalStatus(maritalStatus)
.build())
.build();
}
}
Above code prints
JSON for John:
{
"status" : "married",
"name" : "John"
}
JSON for Tom:
{
"name" : "Tom"
}
XML for John:
<User>
<status type="text">married</status>
<name>John</name>
</User>
XML for Tom
<User>
<name>Tom</name>
</User>
Notice, that we implemented UserStatusJsonSerializer#isEmpty method which defines what empty means for a Status class. Now, we need to enable JsonInclude.Include.NON_EMPTY feature in your Spring Boot application. Add below key to your application configuration file:
spring.jackson.default-property-inclusion=non_empty
If you do not want to enable inclusion globally you can enable it only for one property using #JsonInclude annotation.
#JsonInclude(JsonInclude.Include.NON_EMPTY)
private Status status;
See also:
Using Jackson to add XML attributes to manually-built node-tree
How to tell Jackson to ignore a field during serialization if its value is null?
Spring Boot: Customize the Jackson ObjectMapper
The solution to marshalling an object one way in XML, but another in JSON (different fields, etc.) was to use "mixins".
One trick is that you have to manually register the mixin, there's no magic. See below.
Mixin interface:
public interface UserStatusXmlMixin {
#JsonValue(false)
#JacksonXmlText
String getStatus();
#JacksonXmlProperty(isAttribute = true)
String getType();
}
Implementation:
#Value
public class UserStatus implements UserStatusXmlMixin {
private String status;
#JsonValue
#Override
public String getStatus() {
return status;
}
#Override
public String getType() {
return "text";
}
/**
* Returns an unmodifiable UserStatus when status is available,
* otherwise return null. This will help to remove this object from the responses.
*/
public static UserStatus of(final String status) {
return Optional.ofNullable(status)
.map(UserStatus::new)
.orElse(null);
}
}
I also had to register the "mixin" manually.
#Configuration
public class AppJacksonModule extends SimpleModule {
private static final long serialVersionUID = -1;
private final Map<Class, Class> mixinByTarget;
/**
* Construct an AppJacksonModule.
*/
public AppJacksonModule() {
super("AppJacksonModule");
this.mixinByTarget = Map.of(
UserStatus.class, UserStatusXmlMixin.class
);
}
#Override
public void setupModule(final SetupContext context) {
super.setupModule(context);
final ObjectCodec contextOwner = context.getOwner();
if (contextOwner instanceof XmlMapper) {
mixinByTarget.forEach(context::setMixInAnnotations);
}
}
Now wherever I needed to create UserStatus using UserStatus.of(..) if the input param is null, <status/> won't show up in the response.

org.codehaus.jackson.map.JsonMappingException: Can not instantiate value of type [simple type, class models.Job] from JSON String

i use the playframework and tried to deserialize some json into a java object.
It worked fine, exept the relationship in the model. I got the following exception
enter code hereorg.codehaus.jackson.map.JsonMappingException: Can not
instantiate value of type [simple type, class models.Job] from JSON
String; no single-String constructor/factory method (through reference
chain: models.Docfile["job"])
i thought jackson in combination with play could do that:
this is the json
{"name":"asd","filepath":"blob","contenttype":"image/png","description":"asd","job":"1"}
and this my code, nothing special:
public static Result getdata(String dataname) {
ObjectMapper mapper = new ObjectMapper();
try {
Docfile docfile = mapper.readValue((dataname), Docfile.class);
System.out.println(docfile.name);
docfile.save();
} catch (JsonGenerationException e) {
e.printStackTrace();
} catch (JsonMappingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return ok();
}
Hope there is help for me, thanks
Markus
UPDATE:
Docfile Bean:
package models;
import java.util.*;
import play.db.jpa.*;
import java.lang.Object.*;
import play.data.format.*;
import play.db.ebean.*;
import play.db.ebean.Model.Finder;
import play.data.validation.Constraints.*;
import play.data.validation.Constraints.Validator.*;
import javax.persistence.*;
import com.avaje.ebean.Page;
#Entity
public class Docfile extends Model {
#Id
public Long id;
#Required
public String name;
#Required
public String description;
public String filepath;
public String contenttype;
#ManyToOne
public Job job;
public static Finder<Long,Docfile> find = new Model.Finder(
Long.class, Docfile.class
);
public static List<Docfile> findbyJob(Long job) {
return find.where()
.eq("job.id", job)
.findList();
}
public static Docfile create (Docfile docfile, Long jobid) {
System.out.println(docfile);
docfile.job = Job.find.ref(jobid);
docfile.save();
return docfile;
}
}
Either you change your JSON in order to describe your "job" entity :
{
"name":"asd",
"filepath":"blob",
"contenttype":"image/png",
"description":"asd",
"job":{
"id":"1",
"foo", "bar"
}
}
or you create a constructor with a String parameter in your Job bean:
public Job(String id) {
// populate your job with its id
}
when limited time +ee: +jax-rs && +persistence, +gson; I have solved it then as:
#Entity
#XmlRootElement
#Table(name="element")
public class Element implements Serializable {
public Element(String stringJSON){
Gson g = new Gson();
Element a = g.fromJson(stringJSON, this.getClass());
this.setId(a.getId());
this.setProperty(a.getProperty());
}
public Element() {}
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
...
}

How to serialize ASObject to JSON

I'm trying to serialize and deserialize flex.messaging.io.amf.ASObject to JSON. ASObject extends HashMap and adds an additional type property. By default Jackson correctly serializes all the keys and values under the object, but doesn't preserve the ASObject.getType().
Using Jackson I've managed to create a custom serializer for ASObject and am now serializing as:
[{"#type":"org.me.MyClass","map":{"key":"value"}}]
This was by adding an additional type field then delegating back to the standard handler for java.util.Map. However I'm not sure how I can configure Jackson to allow custom deserialization to allow custom handling of this.
Perhaps I'm going about this the wrong way!
Maybe you want to create custom deserializer as well? You may not really need that type field as long as type is known from context when deserializing (property has ASOBject type).
Here's one approach.
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.JsonProcessingException;
import org.codehaus.jackson.Version;
import org.codehaus.jackson.annotate.JsonAutoDetect.Visibility;
import org.codehaus.jackson.annotate.JsonMethod;
import org.codehaus.jackson.map.DeserializationContext;
import org.codehaus.jackson.map.JsonDeserializer;
import org.codehaus.jackson.map.JsonSerializer;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.SerializerProvider;
import org.codehaus.jackson.map.module.SimpleModule;
public class JacksonFoo
{
public static void main(String[] args) throws Exception
{
ASObject asObject = new ASObject();
asObject.type = Bar.class;
asObject.put("1", "alpha");
asObject.put("TWO", "beta");
SimpleModule module = new SimpleModule("SimpleModule", Version.unknownVersion());
module.addSerializer(ASObject.class, new ASObjectSerializer());
ObjectMapper mapper = new ObjectMapper().withModule(module).setVisibility(JsonMethod.FIELD, Visibility.ANY);
String asObjectJson = mapper.writeValueAsString(asObject);
System.out.println(asObjectJson);
// output: {"type":"com.stackoverflow.q8158528.Bar","map":{"1":"alpha","TWO":"beta"}}
module = new SimpleModule("SimpleModule", Version.unknownVersion());
module.addDeserializer(ASObject.class, new ASObjectDeserializer());
mapper = new ObjectMapper().withModule(module).setVisibility(JsonMethod.FIELD, Visibility.ANY);
ASObject asObjectCopy = mapper.readValue(asObjectJson, ASObject.class);
System.out.println(asObjectCopy.equals(asObject));
// output: true
}
}
class ASObjectDeserializer extends JsonDeserializer<ASObject>
{
#Override
public ASObject deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException
{
ASObject asObject = new ASObject();
JsonNode tree = jp.readValueAsTree();
try
{
asObject.type = Class.forName(tree.get("type").asText());
}
catch (ClassNotFoundException e)
{
System.exit(42);
}
asObject.putAll(jp.getCodec().treeToValue(tree.get("map"), Map.class));
return asObject;
}
}
class ASObjectSerializer extends JsonSerializer<ASObject>
{
#Override
public void serialize(ASObject value, JsonGenerator jgen, SerializerProvider provider) throws IOException,
JsonProcessingException
{
jgen.writeStartObject();
jgen.writeStringField("type", value.type.getName());
jgen.writeObjectField("map", new HashMap(value));
jgen.writeEndObject();
}
}
class ASObject extends HashMap
{
Class type;
#Override
public boolean equals(Object o)
{
ASObject a = (ASObject) o;
return type.equals(a.type) && super.equals(a);
}
}
class Bar
{
}

Spring MVC mapping view for Google-GSON?

Does anyone know if there is a Spring MVC mapping view for Gson? I'm looking for something similar to org.springframework.web.servlet.view.json.MappingJacksonJsonView.
Ideally it would take my ModelMap and render it as JSON, respecting my renderedAttributes set in the ContentNegotiatingViewResolver declaration
We plan to use Gson extensively in the application as it seems safer and better than Jackson. That said, we're getting hung up by the need to have two different JSON libraries in order to do native JSON views.
Thanks in advance!
[cross-posted to Spring forums]
aweigold got me most of the way there, but to concretely outline a solution for Spring 3.1 Java based configuration, here's what I did.
Grab GsonHttpMessageConverter.java from the spring-android-rest-template project.
Register your GsonHttpMessageConverter with the message converters in your MVC config.
#EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new GsonHttpMessageConverter());
}
}
The Spring docs outline this process, but aren't crystal clear. In order to get this to work properly, I had to extend WebMvcConfigurerAdapter, and then override configureMesageConverters. After doing this, you should be able to do the following in your controller method:
#Controller
public class AppController {
#RequestMapping(value = "messages", produces = MediaType.APPLICATION_JSON_VALUE)
public List<Message> getMessages() {
// .. Get list of messages
return messages;
}
}
And voila! JSON output.
I would recommend to extend AbstractView just like the MappingJacksonJsonView does.
Personally, for JSON, I prefer to use #Responsebody, and just return the object rather than a model and view, this makes it easier to test. If you would like to use GSON for that, just create a custom HttpMessageConverter like this:
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonParseException;
import com.google.gson.reflect.TypeToken;
import com.vitalimages.string.StringUtils;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.stereotype.Component;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
import java.sql.Timestamp;
#Component
public class GSONHttpMessageConverter extends AbstractHttpMessageConverter<Object> {
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
private GsonBuilder gsonBuilder = new GsonBuilder()
.excludeFieldsWithoutExposeAnnotation()
.setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ")
.registerTypeAdapter(Timestamp.class, new GSONTimestampConverter());
public GSONHttpMessageConverter() {
super(new MediaType("application", "json", DEFAULT_CHARSET));
}
#Override
protected boolean supports(Class<?> clazz) {
// should not be called, since we override canRead/Write instead
throw new UnsupportedOperationException();
}
#Override
public boolean canRead(Class<?> clazz, MediaType mediaType) {
return MediaType.APPLICATION_JSON.isCompatibleWith(mediaType);
}
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
return MediaType.APPLICATION_JSON.isCompatibleWith(mediaType);
}
public void registerTypeAdapter(Type type, Object serializer) {
gsonBuilder.registerTypeAdapter(type, serializer);
}
#Override
protected Object readInternal(Class<? extends Object> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
try {
Gson gson = gsonBuilder.create();
return gson.fromJson(StringUtils.convertStreamToString(inputMessage.getBody()), clazz);
} catch (JsonParseException e) {
throw new HttpMessageNotReadableException("Could not read JSON: " + e.getMessage(), e);
}
}
#Override
protected void writeInternal(Object o, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
Type genericType = TypeToken.get(o.getClass()).getType();
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputMessage.getBody(), DEFAULT_CHARSET));
try {
// See http://code.google.com/p/google-gson/issues/detail?id=199 for details on SQLTimestamp conversion
Gson gson = gsonBuilder.create();
writer.append(gson.toJson(o, genericType));
} finally {
writer.flush();
writer.close();
}
}
}
And then add it to your converter list in your handler adapter like this:
#Bean
public HandlerAdapter handlerAdapter() {
final AnnotationMethodHandlerAdapter handlerAdapter = new AnnotationMethodHandlerAdapter();
handlerAdapter.setAlwaysUseFullPath(true);
List<HttpMessageConverter<?>> converterList = new ArrayList<HttpMessageConverter<?>>();
converterList.addAll(Arrays.asList(handlerAdapter.getMessageConverters()));
converterList.add(jibxHttpMessageConverter);
converterList.add(gsonHttpMessageConverter);
handlerAdapter.setMessageConverters(converterList.toArray(new HttpMessageConverter<?>[converterList.size()]));
return handlerAdapter;
}