Jackson deserializer for generic type - java

I need to write a custom deserializer for a class with generics. I couldn't find a way to do this, however I cannot imagine I'm the only one with this problem. As far as I've thought through it, there would be two ways to implement this, yet none of them are implementable:
Providing a Class parameter to the deserializer in the constructor of the deserializer doesn't work, because when registering the deserializer, the relationship between Type.class and the instance of the deserializer with the passed Class instance is lost.
For example:
public class Foo<T> {}
public class FooDeserializer<T> {
public FooDeserializer(Class<T> type) { ... }
...
}
// Boilerplate code...
module.addDeserializer(Foo.class, new FooDeserializer<Bar1>(Bar1.class));
module.addDeserializer(Foo.class, new FooDeserializer<Bar2>(Bar2.class));
This doesn't work, when an ObjectMapper instance gets an instance of Foo, there's no type information of the generic parameter available (type erasure) so it simply chooses the last deserializer registered.
It doesn't help to keep a reference of the generic type in the class because an instantiated version of the class cannot be passed to the deserializer (the interface is readValue(String, Class)).
For example:
String json = "...";
ObjectMapper mapper = ...;
Foo<Bar1> foo = new Foo<>(Bar1.class);
foo = mapper.readValue(json, Foo.class); // Can't pass empty foo instance with Class<?> field containing Bar1.class
Something like this would be needed:
mapper.readValue(json, Foo.class, Bar1.class); // Doesn't exist in jackson
Any suggestions how to do this?
EDIT:
I found a way to solve the problem, however it's not a clean solution:
I extend the FooDeserializer with a Class field to save the type of Foo's generic parameter. Then, every time I want to deserialize some json into a new Foo instance, I get a new ObjectMapper instance (I use ObjectMapper#copy on a preconfigured instance from a factory) and pass it a new Module which contains an instance of the FooDeserializer with the class parameter (I know the type at this time). Module, FooDeserializer and the ObjectMapper copy are short living, they only get instantiated for this single deserialization action. As I said, not very clean, but still better than subclassing Foo many times and writing a deserializer for each.
Example:
public class FooDeserializer<T> extends StdDeserializer<T> {
private Class<T> type;
public FooDeserializer(Class<T> type) { this.type = type }
...
}
// Meanwhile, before deserialization:
ObjectMapper mapper = MyObjectMapperFactory.get().copy();
SimpleModule module = new SimpleModule(new Version(0,0,1,null,null,null);
module.addDeserializer(Foo.class, new FooDeserializer(Bar1.class);
mapper.addModule(module);
Foo<Bar1> x = mapper.readValue(json, Foo.class);
Probably putting this into a utility method to hide the uglyness.

I don't think you need to write our own custom deserializer. You can use this syntax to deserialize objects that use generics, taken from another Stack Overflow thread.
mapper.readValue(jsonString, new TypeReference<Data<String>>() {});
Couple sources to help you:
Jackson - Deserialize using generic class
http://www.tutorialspoint.com/jackson/jackson_data_binding_generics.htm

You don't need to write a custom deserializer. A generic can be deserialized by putting Jackson annotations on the type that is passed as parameter to the generic class.
Annotate class Bar1 as follows:
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT)
#JsonTypeName("Bar1")
class Bar1 {
}
Now when you deserialize an instance of Foo<Bar1>, Jackson will put type parameter info into JSON. This JSON can then be deserialized into generic class Foo.class just as you would deserialize any other class.
ObjectMapper mapper = ...;
Foo<Bar1> foo = new Foo<Bar1>();
String json = objectMapper.writeValueAsString(foo);
foo = mapper.readValue(json, Foo.class); // no need to specify the type Bar1.
So if every class that can be passed as parameter to Foo has annotations on it, then JSON can be deserialized without knowing the type parameter at compile time.
Please refer Jackson documentation on Polymorphic Deserialization and Annotations.

Related

Converting Object into "something else" before Serializing it using FasterXML Jackson

public void serialize(IPerson person, OutputStream output) throws Exception {}
public void deserialize(InputStream input) throws Exception {}
I have an interface named IPerson, it has basic functionality.
I want to serialize the person object and be able to deserialize it from the deserialize method.
However, the scenario is this I cannot use Java's serializable interface as I can't be sure what implementation of IPerson will be used.
I have chosen to use Jackson's FasterXML, using ObjectMapper m = new ObjectMapper();
The problem I am having is that since IPerson is an interface I cannot serialize it directly using mapper.writerValue(output, person), I figured I must convert this object into something else, say a ByteArray then serialize it?
Also, this would be converting this something else into an object when deserializing? I have minimal experience with what exactly I should convert this object to and how to do so? Any ideas?
When using the default ObjectMapper you will have to make sure the objects you serialize are Java Beans. For non-bean classes you can set field visibility using m.setVisibility(PropertyAccessor.FIELD, Visibility.ANY); or annotate your class using #JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY).
For deserializing you will have to tell the ObjectMapper the target type. This can be done by providing a concrete implementation type to readValue or by storing the classname within the exported JSON. For this you can set m.enableDefaultTypingAsProperty(DefaultTyping.OBJECT_AND_NON_CONCRETE, "__class"); and annotate your objects with #JsonTypeInfo
ObjectMapper om = new ObjectMapper();
om.enableDefaultTypingAsProperty(DefaultTyping.OBJECT_AND_NON_CONCRETE, "__class");
IPerson value = new MyPerson();
String s = om.writeValueAsString(value);
IPerson d = om.readValue(s, IPerson.class);
using
interface IPerson {
void doSomething();
}
#JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, property = "__class")
#JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
class MyPerson implements IPerson {
String name;
#Override
public void doSomething() {
}
}
Note that, you will need a default constructor for this to work or work with #JsonCreator and #JsonProperty (see jackson-annotations for details)

Jackson deserialization with anonymous classes

I have been searching all day for something that answers this, but I have not had a lot of luck thus far.
My question is straightforward: how do I deserialize an anonymous object correctly using Jackson.
private interface Interface1
{
int getValue();
}
public static void testAnonymousObject() throws IOException
{
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
mapper.setVisibility(PropertyAccessor.ALL, Visibility.NONE);
mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
Interface1 testObject = new Interface1()
{
private final int value = 5;
#Override
public int getValue()
{
return value;
}
};
String json = mapper.writeValueAsString(testObject);
System.out.println("JSON = " + json);
Interface1 received = (Interface1) mapper.readValue(json, Object.class);
System.out.println(received);
}
The output of this is: JSON = ["com.foo.test.JacksonTest$1",{"value":5}] before I get an exception:
Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize Class com.foo.test.JacksonTest$1 (of type local/anonymous) as a Bean.
EDIT Just to clarify, both Jackson and XStream are able to serialize the object. But only XStream seems to be able to deserialize the object back. So this scenario can be made to work.
As of the time I am writing this, it seems that Jackson does not serialize inner classes or anonymous classes correctly. Other packages such as XStream and Kryo, do however.
Because inner classes do not have a default zero argument constructor (they have a hidden reference to the outer/parent class) Jackson cannot instantiate them.
you can check this link
Problem is not just about it being an inner class (which may or may not be problematic, depending on whether implementation is static or non-static), but also in that no type information is included -- all Jackson sees is type Interface1. To enable reading it back it is necessary to either include type information ("polymorphic type handling"), or to specify mapping between abstract type and implementation class.
Given that you are using an anonymous inner class, you would be able to support this usage by enabled so-called "default typing" (see ObjectMapper javadocs for enableDefaultTyping() or such).
But you may also need to implement specific strategy, if you do not want to enable type inclusion for all non-final types.
To see whether type id is included you can enable default typing with one of default options and have a look at JSON being produced: there should be an additional type id ("#class" property when class name is used as id).
A ready-to-use code-snippet for a generic JSON-deserialization to a Java POJO with Jackson using nested classes:
static class MyJSON {
private Map<String, Object> content = new HashMap<>();
#JsonAnySetter
public void setContent(String key, Object value) {
content.put(key, value);
}
}
String json = "{\"City\":\"Prague\"}";
try {
MyPOJO myPOJO = objectMapper.readValue(json, MyPOJO.class);
String jsonAttVal = myPOJO.content.get("City").toString();
System.out.println(jsonAttVal);
} catch (IOException e) {
e.printStackTrace();
}
#JsonAnySetter ensures a generic JSON-parsing and population.

Guava Optional serialization with Jackson

I am using jackson-datatype-guava along with Jackson to handle serialization of Guava objects. It all works fine with other objects but facing issue with Optional.
I have a serializer deserializer interface from a framework with following methods which I am forced to use.
String toJson(Object object);
<T> T fromJson(String json, Class<T> valueType);
That means my Jackson implementation of this interface which uses ObjectMapper also can only use following method on mapper.
mapper.readValue(json, valueType);
Which is not working in case of 'Optional'.
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new GuavaModule());
mapper.readValue(mapper.writeValueAsString(Optional.of("string")), Optional.class);
This gives error saying Null JavaType passed. If I use TypeReferece<Optional<String>> in mapper, it works fine but is there a way to make it work with above interface?
//works fine but can't use this method as interface only accepts `Class` param.
mapper.readValue(mapper.writeValueAsString(Optional.of("string")), new TypeReference<Optional<String>>(){});
Update
I tried using 2.4.* versions of these library as suggested in comments and it's working fine for Optional<String> but does not still work for Optional<MyClass> where MyClass is simple pojo. Seems while deserializing, it thinks, json of MyClass is a map and converts it into Optional<Map>.
public class MyClass {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Main Program:
MyClass obj = new MyClass();
obj.setName("PAC");
Optional<MyClass> obj1 = mapper.readValue(mapper.writeValueAsString(Optional.of(obj)), Optional.class);
System.out.println(obj1); //prints as Map
System.out.println(obj1.get().getName()); //Fails doing casting to MyClass
Output:
Exception in thread "main" Optional.of({name=PAC})
java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to MyClass
at OptionalTest.main(OptionalTest.java:22)
I also tried enabling default typing in mapper by mapper.enableDefaultTyping() but without any luck.
Error at mapper.readValue... line after enabling default typing for DefaultTyping.NON_FINAL
Invalid type id 'MyClass' (for id type 'Id.class'): Class MyClass is not assignable to com.google.common.base.Optional
Any further suggestions on this?
The interface you are forced to use is poor, it needs to support java.lang.reflect.Type in order to allow you to pass generic types. Then you can use jacksons TypeReference (or TypeToken from guava) to obtain an instance of a Optional type.

Jackson Generic Type References + enableDefaultTyping issue

I encountered an deserialization issue when using both enableDefaultTyping and providing a generic TypeRefernce. It seems that Jackson is unable to decide which type information is more important. This Test case is demonstarting the problem:
#Test
public void roundTripTest() throws JsonGenerationException,
JsonMappingException, IOException {
// 0 Value Test
Integer[] integers = new Integer[] {};
Wrap<Integer[]> beforeResult = new Wrap<Integer[]>(integers);
File file = new File("/tmp/jsonTest");
mapper.writeValue(file, beforeResult);
TypeReference<Wrap<Integer[]>> typeRef = new TypeReference<JacksonMapperTest.Wrap<Integer[]>>() {
};
Wrap<Integer[]> afterResult = mapper.readValue(file, typeRef);
assertNotNull(afterResult);
}
public static class Wrap<T> {
private T wrapped;
public Wrap() {
}
public Wrap(T wrapped) {
this.wrapped = wrapped;
}
public T getWrapped() {
return wrapped;
}
public void setWrapped(T wrapped) {
this.wrapped = wrapped;
}
}
where mapper is :
mapper = new ObjectMapper();
mapper.enableDefaultTyping();
and the exception is:
org.codehaus.jackson.map.JsonMappingException: No suitable constructor found for type [simple type, class JacksonMapperTest$Wrap<[Ljava.lang.Integer;>]: can not instantiate from JSON object (need to add/enable type information?)
pretty weird, eh? by using beforeResult.getClass instead of TypeRefernce it is possible to omit the issue, but it is still not the prefered behavior.
Did I miss any kind of options in order to solve this?
I was using Jackson 1.9.3
[EDIT]
Using Maps Instead of an Array as Wrapped Objects works as expected!
This seems to be a bug in jackson...however its author is probably more competent to tell if it is a bug or not... if you are free to change from jackson take a look at genson http://code.google.com/p/genson/.
Gensons equivalent of enableDefaultTyping is setUseRuntimeTypeForSerialization (not exactly the same but it is very similar in most cases). It uses the runtime type during serialization. Here is an example:
Genson genson = new Genson.Builder().setUseRuntimeTypeForSerialization(true).create();
String json = genson.serialize(beforeResult);
System.out.println(json);
GenericType<Wrap<Integer[]>> type = new GenericType<Wrap<Integer[]>>() {};
Wrap<Integer[]> afterResult = genson.deserialize(json, type);
EDIT
If you need to be able to deserialize to polymoprhic or unknown types with genson use setWithClassMetadata(true) of Genson.Builder. This feature actually is used only for json objects (no matter if it is an abstract class or not).
I am not 100% sure of exact root cause, but I suspect this has to do with Java type erasure, and problems from passing generics-aware information and mixing it with default type information. Type info is actually only based on non-generic types; however, it does work for specific kinds of generic types (Maps, Collections).
But in this case you have custom generic type; and I think this is what makes Jackson unable to indicate generic type of property for purposes of dynamic typing.
The usual workaround is to try sub-classing if possible; but fortunately you were able to find a better workaround.

Wrap certain values when serializing JSON with Gson

How can I wrap certain values when serializing an object model to JSON with GSON? Example model:
class Order {
Customer cust;
}
class Customer {
String name;
int age;
}
Serializing a Customer would normally yield something like:
{cust:{name:joe, age:21}}
What I would like to do is wrap the Order and Customer values in an additional element with the class name. So the expected JSON would be:
{Order:{cust:Customer:{name:joe, age:21}}}
The actual classes that I'll be serializing could be anything, so I can't hardcode specific properties in a serializer. But I will want to wrap certain properties with their class name.
How can I do this?
This is not a valid json string though:
{Order:{cust:Customer:{name:joe, age:21}}}
In you application, you have to model/implement your domain class properly before it can be used for serializing/deserializing json string, in another word, the domain class should be known before calling toJson/fromJson method. However the actual domain class type can be determined dynamically at runtime, By using Java Generic Type.
Check out the section Serializing and Deserializing Generic Types from Gson User Guide:
public class Foo<T> {
public T value;
}
To serialize it:
Gson gson = new Gson();
Type fooType = new TypeToken<Foo<Bar>>() {}.getType();
gson.toJson(foo, fooType);
To deserialize it:
Gson gson = new Gson();
Type fooType = new TypeToken<Foo<Bar>>() {}.getType();
gson.fromJson(json, fooType);

Categories

Resources