Serializing and deserializing Java generics using Jackson: StackOverflowError - java

I'm currently working on a model which uses generics and is little complicated. I understand that similar questions have been answered but none of them clearly answers mine.
Here is my model:
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = As.WRAPPER_OBJECT, property = "type")
#JsonSubTypes(
{
#Type(value = Cls2.class, name = "Cls2")
})
abstract class Cls1<T> implements Serializable
{
private T myObj;
public T getMyObj()
{
return myObj;
}
public Cls1(T obj)
{
myObj = obj;
}
#JsonTypeName("Cls2")
public static class Cls2<E extends Int1> extends Cls1<E> implements Serializable
{
public Cls2()
{
super(null);
}
}
}
#JsonTypeName("ChildContainer")
class ChildContainer extends ParentContainer<OtherBean>
{
}
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = As.WRAPPER_OBJECT, property = "type")
#JsonSubTypes(
{
#Type(value = ChildContainer.class, name = "ChildContainer")
})
class ParentContainer<T extends RootBean> implements Int1
{
}
#JsonTypeName("OtherBean")
class OtherBean extends RootBean
{
}
#JsonTypeName("RootBean")
class RootBean implements Int1
{
}
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = As.WRAPPER_OBJECT, property = "type")
#JsonSubTypes(
{
#Type(value = RootBean.class, name = "RootBean"),
#Type(value = OtherBean.class, name = "OtherBean")
})
interface Int1 extends Serializable
{
}
My goal is to serialize and deserialze using jackson as follows:
public static void main(String[] args) throws Exception
{
Cls2<ChildContainer> req = new Cls2<ChildContainer>();
File file = new File("==some-file-path==");
ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(file, req);
//read it back using mapper.readValue(file, clazz) --Not sure about this
}
I get the following java.lang.StackOverflowError during the serialization:
Exception in thread "main" java.lang.StackOverflowError
at java.lang.Class.getDeclaringClass(Native Method)
at org.codehaus.jackson.map.type.TypeBindings._resolveBindings(TypeBindings.java:290)
at org.codehaus.jackson.map.type.TypeBindings._resolve(TypeBindings.java:221)
at org.codehaus.jackson.map.type.TypeBindings.findType(TypeBindings.java:138)
at org.codehaus.jackson.map.type.TypeFactory._fromVariable(TypeFactory.java:951)
at org.codehaus.jackson.map.type.TypeFactory._constructType(TypeFactory.java:493)
at org.codehaus.jackson.map.type.TypeFactory.findTypeParameters(TypeFactory.java:423)
at org.codehaus.jackson.map.type.TypeFactory.findTypeParameters(TypeFactory.java:395)
at org.codehaus.jackson.map.type.TypeBindings._resolveBindings(TypeBindings.java:299)
at org.codehaus.jackson.map.type.TypeBindings._resolveBindings(TypeBindings.java:290)
at org.codehaus.jackson.map.type.TypeBindings._resolve(TypeBindings.java:221)
at org.codehaus.jackson.map.type.TypeBindings.findType(TypeBindings.java:138)
at org.codehaus.jackson.map.type.TypeFactory._fromVariable(TypeFactory.java:951)
at org.codehaus.jackson.map.type.TypeFactory._constructType(TypeFactory.java:493)
at org.codehaus.jackson.map.type.TypeFactory.findTypeParameters(TypeFactory.java:423)
at org.codehaus.jackson.map.type.TypeFactory.findTypeParameters(TypeFactory.java:395)
at org.codehaus.jackson.map.type.TypeBindings._resolveBindings(TypeBindings.java:299)
at org.codehaus.jackson.map.type.TypeBindings._resolveBindings(TypeBindings.java:290)
Any help is deeply appreciated.

The stacktrace suggests an infinite recursion in type resolving of Cls2 which extends the class it is by itself nested in. This seems to be a corner case bug in Jackson (report it!). In the meanwhile, extracting Cls2 into a standalone class instead of nesting it in its superclass should solve this problem.

Related

JsonTypeInfo not written for an object in a collection

I'm trying to serialize/deserialize a polymorphic type with Jackson 2.9.8, and it works fine unless I put an object of such type into a collection, because for some reason type info is not written then. Let's consider the following example:
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "animalKind")
#JsonSubTypes({
#JsonSubTypes.Type(value = Dog.class, name = "Dog")
})
public interface Animal {
}
#JsonIgnoreProperties(ignoreUnknown = true)
public class Dog implements Animal {
private Boolean goodBoy;
public Boolean isGoodBoy() { return goodBoy; }
public void setGoodBoy(Boolean goodBoy) { this.goodBoy = goodBoy; }
}
Now let's serialize an instance of Dog:
ObjectMapper objectMapper = new ObjectMapper();
Dog mike = new Dog();
mike.setGoodBoy(true);
// This works just fine
String mikeJson = objectMapper.writeValueAsString(mike);
System.out.println(mikeJson);
// This doesn't work
String listJson = objectMapper.writeValueAsString(Collections.singleton(mike));
System.out.println(listJson);
// This doesn't either
String mapJson = objectMapper.writeValueAsString(Collections.singletonMap("Mike", mike));
System.out.println(mapJson);
The output is the following:
{"animalKind":"Dog","goodBoy":true}
[{"goodBoy":true}]
{"Mike":{"goodBoy":true}}
So the animalKind is written in the first case but it's not in the second and the third case. Am I missing some serialization settings here or is it a bug?
Thank you!
You need to instruct Jackson that you need given collection with reading abstract type annotation. See example:
CollectionType animalsListType = mapper.getTypeFactory()
.constructCollectionType(Set.class, Animal.class);
System.out.println(mapper.writer().withType(animalsListType).writeValueAsString(Collections.singleton(mike)));
Map<String, Dog> mikeMap = Collections.singletonMap("Mike", mike);
MapType mapType = mapper.getTypeFactory().constructMapType(Map.class, String.class, Animal.class);
System.out.println(mapper.writer().withType(mapType).writeValueAsString(mikeMap));
Above code prints:
[{"animalKind":"Dog","goodBoy":true}]
{"Mike":{"animalKind":"Dog","goodBoy":true}}

How to deserialize JSON to interface?

I have trouble with deserialization JSON to some of classes ChildA, ChildB and etc. that implements Basic interface in following example.
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = InstagramUser.class, name = "ChildA")
})
public interface Basic {
getName();
getCount();
}
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonTypeName("ChildA")
public class ChildA implements Basic { ... }
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonTypeName("ChildB")
public class ChildB implements Basic { ... }
...
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true)
public class Response<E extends Basic> {
#JsonProperty("data")
private List<E> data;
public List<E> getData() {
return data;
}
public void setData(List<E> data) {
this.data = data;
}
}
// deserialization
HTTPClient.objectMapper.readValue(
response,
(Class<Response<ChildA>>)(Class<?>) Response.class
)
Exception is: com.fasterxml.jackson.databind.JsonMappingException: Unexpected token (END_OBJECT), expected FIELD_NAME: missing property 'type' that is to contain type id (for class Basic)
Expected JSON is like this:
{
"data": [{ ... }, ...]
}
There is no property that is presented in all type objects so they are completely different. But as you can see on readValue line I know what is expected type. How to structure JsonTypeInfo and JsonSubTypes annotaions to deserialize JSON as expected class?
I kinda had the same problem as you, based in the reading here: Jackson Deserialize Abstract Classes I created my own solution, it basically consists of creating my own deserializer, the trick is to use/identify a specific property within JSON to know which instance type should be returned from deserialization, example is:
public interface Basic {
}
First Child:
public class ChildA implements Basic {
private String propertyUniqueForThisClass;
//constructor, getters and setters ommited
}
SecondChild:
public class ChildB implements Basic {
private String childBUniqueProperty;
//constructor, getters and setters ommited
}
The deserializer (BasicDeserializer.java) would be like:
public class BasicDeserializer extends StdDeserializer<Basic> {
public BasicDeserializer() {
this(null);
}
public BasicDeserializer(final Class<?> vc) {
super(vc);
}
#Override
public Basic deserialize(final JsonParser jsonParser,
final DeserializationContext deserializationContext)
throws IOException {
final JsonNode node = jsonParser.getCodec().readTree(jsonParser);
final ObjectMapper mapper = (ObjectMapper) jsonParser.getCodec();
// look for propertyUniqueForThisClass property to ensure the message is of type ChildA
if (node.has("propertyUniqueForThisClass")) {
return mapper.treeToValue(node, ChildA.class);
// look for childBUniqueProperty property to ensure the message is of type ChildB
} else if (node.has("childBUniqueProperty")) {
return mapper.treeToValue(node, ChildB.class);
} else {
throw new UnsupportedOperationException(
"Not supported class type for Message implementation");
}
}
}
Finally, you'd have an utility class (BasicUtils.java):
private static final ObjectMapper MAPPER;
// following good software practices, utils can not have constructors
private BasicUtils() {}
static {
final SimpleModule module = new SimpleModule();
MAPPER = new ObjectMapper();
module.addDeserializer(Basic.class, new BasicDeserializer());
MAPPER.registerModule(module);
}
public static String buildJSONFromMessage(final Basic message)
throws JsonProcessingException {
return MAPPER.writeValueAsString(message);
}
public static Basic buildMessageFromJSON(final String jsonMessage)
throws IOException {
return MAPPER.readValue(jsonMessage, Basic.class);
}
For testing:
#Test
public void testJsonToChildA() throws IOException {
String message = "{\"propertyUniqueForThisClass\": \"ChildAValue\"}";
Basic basic = BasicUtils.buildMessageFromJSON(message);
assertNotNull(basic);
assertTrue(basic instanceof ChildA);
System.out.println(basic);
}
#Test
public void testJsonToChildB() throws IOException {
String message = "{\"childBUniqueProperty\": \"ChildBValue\"}";
Basic basic = BasicUtils.buildMessageFromJSON(message);
assertNotNull(basic);
assertTrue(basic instanceof ChildB);
System.out.println(basic);
}
The source code can be found on: https://github.com/darkstar-mx/jsondeserializer
I find not exactly solution but a workaround. I used custom response class ChildAResponse and passed it to ObjectMapper.readValue() method.
class ChildAResponse extends Response<ChildA> {}
// deserialization
HTTPClient.objectMapper.readValue(
response,
ChildAResponse.class
)
So JsonTypeInfo and JsonSubTypes annotations on the interface are no longer needed.

Jackson XML - deserializing empty classes and polymorphism

I have the following interface:
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT)
#JsonSubTypes({
#JsonSubTypes.Type(value = EmptyProxy.class, name = "empty"),
... other types not included ...
})
public interface Proxy {
}
I have the following implementation:
#JsonTypeName("empty")
public static class EmptyProxy implements Proxy {
}
As you can see, it is just an empty class. I left the other (working) implementations out of this example.
I have the following container data class:
public static class Data {
#JacksonXmlProperty(localName = "name")
private String name;
#JacksonXmlProperty(localName = "proxy")
private Proxy proxy;
}
Deserializing EmptyProxy does not seem to work. For example:
final ObjectMapper mapper = new XmlMapper().registerModule(new JacksonXmlModule());
final Data data = mapper.readValue("<data><name>my-name</name><proxy><empty/></proxy></data>", Data.class);
This gives the following exeption:
Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of Test$EmptyProxy out of VALUE_NULL token
at [Source: java.io.StringReader#59ec2012; line: 1, column: 42] (through reference chain: Data["proxy"])
at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:148)
at com.fasterxml.jackson.databind.DeserializationContext.mappingException(DeserializationContext.java:857)
Is this a bug in Jackson? FWIW, when I add a dummy field to EmptyProxy, it works.
update
I tried with JAXB only, and get the same result. Code:
public static class Data {
#XmlElement(name = "name")
private String name;
#XmlElements({
#XmlElement(type = EmptyProxy.class, name = "empty")
})
private Proxy proxy;
}
public interface Proxy {
}
#XmlType(name = "empty")
public static class EmptyProxy implements Proxy {
}
public static void main(String[] a) throws IOException {
final ObjectMapper mapper = new XmlMapper()/*.registerModule(new JacksonXmlModule())*/.registerModule(new JaxbAnnotationModule());
final Data data = mapper.readValue("<data><name>my-name</name><proxy><empty></empty></proxy></data>", Data.class);
}
I have created a bug entry for this. See http://github.com/FasterXML/jackson-dataformat-xml/issues/169.

multi level polymophic trees with jackson serialization/deserialization

is it possible to have a complex inheritance structure serialized and deserialized with jackson? what are the annotations for it? for example if I had the following classes
#Inheritance
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
#JsonSubTypes({
#Type(value = Dog.class,name = "dog")
#Type(value = Cat.class,name = "cat")
})
public class Animal implements Serializable{
#id
long id;
String name;
}
this of course is the parent class. I know this is correct if cat and dog do not have any inheriting classes. if I want subclasses of dogs what would I need to change in both the animal class and the dog class?
here is the second class just for reference
#JsonTypeName("dog")
public class Dog extends Animal implements Serializable{
//all my props etc here
{
how would I make a retriever class and a yorki class that inherit from both animal and dog that I could cast to either one and have jackson not freak out at me.
Multi-level polymorphic tree should not be a problem with Jackson. Here is an example of the serializing / de-serializing class hierarchy similar to what you have in your question as it stands in the Jackson wiki page.
public class JacksonPolymorphism3 {
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
public abstract static class Animal {
public final String animalField;
#JsonCreator
public Animal(#JsonProperty("animalField") String animalField) {
this.animalField = animalField;
}
}
#JsonTypeName("dog")
public static class Dog extends Animal {
public final String dogField;
public Dog(#JsonProperty("animalField") String animalField,
#JsonProperty("dogField") String dogField) {
super(animalField);
this.dogField = dogField;
}
#Override
public String toString() {
return "Dog{" +
"dogField='" + dogField + '\'' +
'}';
}
}
#JsonTypeName("husky")
public static class Husky extends Dog {
public final String huskyField;
public Husky(#JsonProperty("animalField") String animalField,
#JsonProperty("dogField") String dogField,
#JsonProperty("huskyField") String huskyField) {
super(animalField, dogField);
this.huskyField = huskyField;
}
#Override
public String toString() {
return "Husky{" +
"huskyField='" + huskyField + '\'' +
'}';
}
}
public static void main(String[] args) throws IOException {
List<Dog> dogs;
dogs = Arrays.asList(new Dog("aField", "dogField"), new Husky("hField", "dField2", "hField"));
ObjectMapper mapper = new ObjectMapper();
mapper.registerSubtypes(Dog.class, Husky.class);
TypeReference<List<Dog>> referenceType = new TypeReference<List<Dog>>() {
};
String json = mapper.writerWithDefaultPrettyPrinter().withType(referenceType).writeValueAsString(dogs);
System.out.println(json);
System.out.println(mapper.readValue(json, referenceType));
}
}
Output:
[ {
"type" : "dog",
"animalField" : "aField",
"dogField" : "dogField"
}, {
"type" : "husky",
"animalField" : "hField",
"dogField" : "dField2",
"huskyField" : "hField"
} ]
[Dog{dogField='dogField'}, Husky{huskyField='hField'}]
If it doesn't help please provide more code.

Error serializing Typed collection with Jackson

I'm trying to serialize a collection using mixings, but Jackson won't save the type info. This is a basic test illustrating what happens:
public class CollectionSerializationTest {
interface Common extends Serializable {
}
class A implements Common {
private static final long serialVersionUID = 1L;
}
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
#JsonSubTypes({ #JsonSubTypes.Type(value = A.class, name = "CODE") })
class AMixIn {
}
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
#JsonSubTypes({ #JsonSubTypes.Type(value = B.class, name = "CODE") })
class BMixIn {
}
class B implements Common {
private static final long serialVersionUID = 1L;
}
#Test
public void test() throws JsonGenerationException, JsonMappingException,
IOException {
ObjectMapper om = new ObjectMapper();
List<Common> list = new ArrayList<Common>();
A a = new A();
B b = new B();
list.add(a);
list.add(b);
om.getSerializationConfig().addMixInAnnotations(A.class, AMixIn.class);
om.getSerializationConfig().addMixInAnnotations(B.class, BMixIn.class);
System.out.println(om.writeValueAsString(list)); // Outputs [{},{}]
System.out.println(om.writeValueAsString(a));// Outputs {"type":"CODE"}
}
}
How do I achieve the output [{"type":"CODE"},{"type":"CODE"}] on the first output?
I am not sure whether it is simplest solution, but I think that you can do it in this way:
Create new List implementation
Write serializer for new type
Use this type in your POJO classes
New Java class which extends ArrayList:
#JsonSerialize(using = JacksonListSerializer.class)
class JacksonList<E> extends ArrayList<E> {
private static final long serialVersionUID = 1L;
}
Serializer for above class:
class JacksonListSerializer extends JsonSerializer<JacksonList<?>> {
#Override
public void serialize(JacksonList<?> list, JsonGenerator generator, SerializerProvider provider)
throws IOException, JsonProcessingException {
generator.writeStartArray();
if (list != null) {
for (Object item : list) {
generator.writeObject(item);
}
}
generator.writeEndArray();
}
}
Now, you can use this list in your example:
public static void main(String[] args) throws IOException {
ObjectMapper om = new ObjectMapper();
List<Common> list = new JacksonList<Common>();
list.add(new A());
list.add(new B());
om.addMixInAnnotations(A.class, AMixIn.class);
om.addMixInAnnotations(B.class, BMixIn.class);
System.out.println(om.writeValueAsString(list));
System.out.println(om.writeValueAsString(new A()));
}
Above example prints:
[{"type":"CODE"},{"type":"CODE"}]
{"type":"CODE"}

Categories