Spring MVC mapping view for Google-GSON? - json

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;
}

Related

Serializing and Deserializing Lambda with Jackson

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.

How to avoid null objects in my REST JSON response?

I'm getting something like this in my JSON response (I'm having a REST implementation in SpringBoot):
"estimatedDeliveryTimeWindow":{
"window":{}
}
I have set custom HTTPMessageCOnverters and configured objectMapper like this:
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
Also tried to remove default converters using below code:
#Bean
public HttpMessageConverters converters() {
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
jsonConverter.setObjectMapper(objectMapper);
return new HttpMessageConverters(false, Arrays.asList(jsonConverter));
}
Nothing seems to work. I still see null objects within objects. These objects are complex objects nested with primitive types and custom objects. What else I can try?
Please add #JsonInclude(Include.NON_NULL) before the class files
#JsonInclude(Include.NON_NULL)
public class MobileLoginVO {
private String otpDetailsId;
public String getOtpDetailsId() {
return otpDetailsId;
}
public void setOtpDetailsId(String otpDetailsId) {
this.otpDetailsId = otpDetailsId;
}
}
You need to inform somehow to spring to use your message converter.
This should do the work:
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;
#Configuration
#EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
public MappingJackson2HttpMessageConverter messageConverter() {
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
jsonConverter.setObjectMapper(objectMapper);
return jsonConverter;
}
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(messageConverter());
}
}

Make #JsonView annotation on controller action include properties by default

When you use Jackson's writerWithView any properties that don't have a #JsonView annotation on them are still serialised. However using #JsonView on a Spring MVC action seems to require #JsonView to be on every property.
If say we have the following model:
public class User {
private String username;
private String emailAddress;
public String getUsername() { return username; }
#JsonView(DetailView.class)
public String getEmailAddress() { return emailAddress; }
}
And DetailView extends BasicView, when I serialise with basic view I'd expect username to be serialised. This is what happens when we use writerWithView:
#RequestMapping(value = "/me", method = GET)
#ResponseBody
public String getMe() throws JsonProcessingException {
User user = getCurrentUser();
return objectMapper.writerWithView(BasicView.class).writeValueAsString(user);
}
However from Spring MVC 4.1 we can instead do the following:
#RequestMapping(value = "/me", method = GET)
#ResponseBody
#JsonView(BasicView.class)
public User getMe() throws JsonProcessingException {
return getCurrentUser();
}
The later causes the response to be {} rather than {username:"David"}. If we add #JsonView(BasicView.class) onto the getUsername() this works as expected.
Obviously we could go with the former or add #JsonView to everything, both of which are more verbose and error prone.
This looks a bit like MapperFeature.DEFAULT_VIEW_INCLUSION has been turned off, but explicitly enabling it doesn't seem to have worked.
Is there anyway to get around this?
MapperFeature.DEFAULT_VIEW_INCLUSION being disabled is indeed the problem, but unfortunately the Spring classes don't give an easy way to configure or replace the ObjectMapper used by the default message converters.
The neatest way I found to work around this was to extend DelegatingWebMvcConfiguration and override configureMessageConverters to populate the default converters and then overwrite the problematic MappingJackson2HttpMessageConverter:
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter;
import org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration;
import com.example.config.serialization.AllEncompassingFormHttpMessageConverterWithCustomObjectMapper;
import java.util.List;
#Configuration
#ComponentScan({/*...*/})
public class MyWebConfig extends DelegatingWebMvcConfiguration {
#Bean
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
//...
return objectMapper;
}
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
super.configureMessageConverters(messageConverters);
if (messageConverters.isEmpty()) {
addDefaultHttpMessageConverters(messageConverters);
}
messageConverters.replaceAll(converter -> {
if (converter instanceof MappingJackson2HttpMessageConverter) {
return new MappingJackson2HttpMessageConverter(objectMapper());
} else if (converter instanceof AllEncompassingFormHttpMessageConverter) {
return new AllEncompassingFormHttpMessageConverterWithCustomObjectMapper(objectMapper());
}
return converter;
});
}
}
For completeness you may also want to substitute the AllEncompassingFormHttpMessageConverter, as above, with your own copy that also allows specification of an ObjectMapper. I've not included the AllEncompassingFormHttpMessageConverterWithCustomObjectMapper class here - it's a trivial copy of AllEncompassingFormHttpMessageConverter that forwards an ObjectMapper constructor parameter to the MappingJackson2HttpMessageConverter that it creates.

Jackson Object Mapper in spring MVC not working

Every object with Date format is being serialized as a long.
I've read around that I need to create a custom object mapper
and so I did:
public class CustomObjectMapper extends ObjectMapper {
public CustomObjectMapper() {
super();
configure(Feature.WRITE_DATES_AS_TIMESTAMPS, false);
setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
}
}
I've also registered that custom mapper as a converter
#Override
protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(converter());
addDefaultHttpMessageConverters(converters);
}
#Bean
MappingJacksonHttpMessageConverter converter() {
MappingJacksonHttpMessageConverter converter = new MappingJacksonHttpMessageConverter();
converter.setObjectMapper(new CustomObjectMapper());
return converter;
}
but still, it doesn't work, and I recieve a long as a date.
Any idea what am I doing wrong?
You'll need to implement your own Dateserializer, just like the following (got it from this tutorial, so props to Loiane, not me ;-) ):
package ....util.json;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.map.JsonSerializer;
import org.codehaus.jackson.map.SerializerProvider;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
#Component
public class JsonDateSerializer extends JsonSerializer<Date>{
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("dd.MM.yyyy HH:mm "); // change according to your needs
#Override
public void serialize(Date date, JsonGenerator gen, SerializerProvider provider)
throws IOException {
String formattedDate = dateFormat.format(date);
gen.writeString(formattedDate);
}
}
then you could just add the following annotation to your Date-Objects and it will persist fine:
#JsonSerialize(using = JsonDateSerializer.class)
public Date getCreated() {
return created;
}
At least it works with spring 3.2.4 and jackson 1.9.13 here.
edit: Think about using FastDateFormat instead of SimpleDateFormat, for it's the threadsafe-alternative (as mentioned in the comments of Loianes article)
Try adding 0 as index in #add()
#Configuration
#ComponentScan()
#EnableWebMvc
#PropertySource("classpath:/web.properties")
public class WebConfig extends WebMvcConfigurerAdapter
{
#Override
public void configureMessageConverters(final List<HttpMessageConverter<?>> converters)
{
converters.add(0, jsonConverter());
}
#Bean
public MappingJacksonHttpMessageConverter jsonConverter()
{
final MappingJacksonHttpMessageConverter converter = new MappingJacksonHttpMessageConverter();
converter.setObjectMapper(new CustomObjectMapper());
return converter;
}
}
It worked for me.

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
{
}