Deserialization JAXBElement with jackson - java

I have a such model:
public class Type {
#XmlElementRef(name = "ConversationId", namespace = "...", type = JAXBElement.class, required = false)
protected JAXBElement<String> conversationId;
}
and part of json:
"conversationId": {
"declaredType": "java.lang.String",
"globalScope": false,
...,
"value": "ABC000000001"
},
I tried use this mapper for deserialization:
public static interface JAXBElementMixin {
#JsonValue
Object getValue();
}
ObjectMapper mapper = new ObjectMapper();
JaxbAnnotationIntrospector jaxbAnnotationIntrospector=new JaxbAnnotationIntrospector(mapper.getTypeFactory());
JacksonAnnotationIntrospector jacksonAnnotationIntrospector=new JacksonAnnotationIntrospector();
mapper.setAnnotationIntrospector(AnnotationIntrospector.pair(jaxbAnnotationIntrospector, jacksonAnnotationIntrospector));
mapper.addMixIn(JAXBElement.class, JAXBElementMixin.class);
But in any case, my program doesn't work well I got:
com.fasterxml.jackson.databind.JsonMappingException: No suitable constructor found for type [simple type, class javax.xml.bind.JAXBElement]: can not instantiate from JSON object (missing default constructor or creator, or perhaps need to add/enable type information?)
I wanted to read some docs about mixins, but there is outdated information on the offcial jackson's website, I use 2.5.1 version

I had the same issue and finally was able to figure it out. Two things need to happen in order for this to work:
The Mixin class MUST be in the separate file, not an inner or
anonymous class.
The Mixin class MUST be in a different package,
i.e. if the code which creates the mapper is in the package
com.foo.bar then Mixin class MUST NOT be in this package.
Here's my code:
package com.foo.bar;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import javax.xml.bind.JAXBElement;
import javax.xml.namespace.QName;
#JsonIgnoreProperties(value = {"globalScope", "typeSubstituted", "nil", "scope"})
public abstract class JAXBElementMixIn<T> extends JAXBElement<T> {
#JsonCreator
public JAXBElementMixIn(#JsonProperty("name") QName name,
#JsonProperty("declaredType") Class<T> declaredType,
#JsonProperty("value") T value) {
super(name, declaredType, value);
}
}
and the code which uses it:
package x.y.z;
...
ObjectMapper mapper = new ObjectMapper();
mapper.addMixIn(JAXBElement.class, JAXBElementMixIn.class);

Related

Jackson ignore subtype property when deserialize

I'm using a subType property in Jackson, and I want to using this property when deserializing json.
package com.gaosoft.ai.kg.commons.sphinx.strategy;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.sankuai.ai.kg.commons.sphinx.model.FAQRecord;
import com.sankuai.ai.kg.commons.sphinx.model.FAQRequest;
import org.springframework.beans.factory.BeanFactory;
import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
property = "strategyType"
)
#JsonSubTypes({
#JsonSubTypes.Type(value = StrategyEmpty.class, name = "empty"),
#JsonSubTypes.Type(value = StrategyNormal.class, name = "normal"),
#JsonSubTypes.Type(value = StrategyDummy.class, name = "dummy")
}
)
public abstract class Strategy implements Serializable {
private String strategyName;
private String strategyType;
private Map<String, Object> args = new HashMap<>();
public String getStrategyType() {
return strategyType;
}
public void setStrategyType(String strategyType) {
this.strategyType = strategyType;
}
public Map<String, Object> getArgs() {
return args;
}
public void setArgs(Map<String, Object> args) {
this.args = args;
}
public String getStrategyName() {
return strategyName;
}
public void setStrategyName(String strategyName) {
this.strategyName = strategyName;
}
public abstract void init(BeanFactory beanFactory);
public abstract List<FAQRecord> fetchFAQ(FAQRequest request);
}
Like my code says, there are 3 subtype of abstract class Strategy, and I want to retain the subclass type name in strategyType property.
Is there a way to fill strategyType when using jackson in this way?
(Sorry about my poor English)
I think what you're asking for is the #JsonTypeInfo#visible property:
Note on visibility of type identifier: by default, deserialization (use during reading of JSON) of type identifier is completely handled by Jackson, and is not passed to deserializers. However, if so desired, it is possible to define property visible = true in which case property will be passed as-is to deserializers (and set via setter or field) on deserialization.
So in your example,
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
property = "strategyType",
visible = true
)
That said, this seems like a design smell. Is it truly valid that you can set a StrategyEmpty's strategyType to dummy? If not, and StrategyEmpty should always have a strategyType of empty, then why not just have an abstract getStrategyType() that each subclass implements with a hardcoded value?

Convert Json to Pojo implementing marker interface

I would need to convert JSON to POJO, which implements following marker interface:
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import java.io.Serializable;
#JsonTypeInfo(
use = JsonTypeInfo.Id.CLASS,
include = JsonTypeInfo.As.PROPERTY,
property = "#class"
)
public interface MyInterface extends Serializable {
}
this is pojo:
public class MyPojo implements MyInterface
{
private static final long serialVersionUID = 1L;
private String phonetype;
private String cat;
//getters + setters
}
and this is snippet code where I have to use it, but I don't know what I have to do:
String json = "{\"phonetype\":\"N95\",\"cat\":\"WP\"}";
ObjectMapper mapper = new ObjectMapper();
MyPojo myPojo = mapper.readValue(json, MyPojo.class);//here I get exception: com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Missing type id when trying to resolve subtype of [simple type, class path.to.MyPojo]: missing type id property '#class'
// at [Source: (String)"{"phonetype":"N95","cat":"WP"}"; line: 1, column: 30]
When I don't implement interface into MyPojo, everything works correctly without exception, but I would need implement it.
I use library jackson-databind-2.9.8
I would like to asking you, if you have any suggestion how can I solve it or if does it exist another approach how parse Pojo implementing interface showed above. I mean something missing in my interface but I don't know what.
Thank you so much
You may try to use a custom deserializer as shown in the following link How arbitrary JSON string can be deserialized to java POJO?

ClassCastException when deserializing with ObjectMapper onto parametrized class, even using TypeReference and TypeFactory

I'm getting the following exception when trying to deserialize with ObjectMapper onto a parameterized class (works fine for non-parametrized classes):
java.lang.ClassCastException: class java.util.LinkedHashMap cannot be cast to class com.xyz.A (java.util.LinkedHashMap is in module java.base of loader 'bootstrap'; com.xyz.A is in unnamed module of loader 'app')
Here's the original code:
Foo<A> request = OBJECT_MAPPER.readValue(payload, Foo.class);
I tried:
Foo<A> request = OBJECT_MAPPER.readValue(payload, new TypeReference<Foo<A>>() {});
As well as:
JavaType myType = OBJECT_MAPPER.getTypeFactory()
.constructParametricType(Foo.class, A.class);
Foo<A> request = OBJECT_MAPPER.readValue(payload, myType);
But I still get the same exception.
Could there be something special about my scenario that's not covered in these questions?
Jackson is not deserialising a generic list that it has serialised
Jackson and generic type reference
Jackson - Deserialize using generic class
One thing I can think of is that my Foo is actually an #AutoMatter-annotated interface that generates the class:
#AutoMatter
public interface Foo<T> {
Optional<T> parent;
Optional<List<T>> children;
}
Normally we have no issues mapping onto AutoMatter-generated classes though. It's just adding the parametrization <T> that seems to be causing issues.
Does anyone have an idea?
Edit to answer #MichalZiober's questions:
In my test code I'm actually just serializing what I know is a valid object, i.e. then deserializing that to get back the object I started with:
Foo<A> myExampleObject;
ByteString.encodeUtf8(OBJECT_MAPPER.writeValueAsString(myExampleObject));
Edit 2
Okay, so it looks like we are already importing that module:
#VisibleForTesting
public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper()
.registerModule(new JodaModule())
.registerModule(new GuavaModule())
.registerModule(new AutoMatterModule())
.registerModule(new Jdk8Module())
.registerModule(new ProtobufModule())
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false)
.configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false)
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
When you use Optional in POJO structure you need to enable Jdk8Module from jackson-modules-java8. Below example shows that with this module registered we can serialise and deserialise data:
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import io.norberg.automatter.AutoMatter;
import io.norberg.automatter.jackson.AutoMatterModule;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
public class JsonApp {
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
mapper.registerModule(new AutoMatterModule());
mapper.registerModule(new Jdk8Module());
String json = "{\"parent\":\"Aaaa\", \"children\":[\"a\"]}";
System.out.println(mapper.readValue(json, Foo.class));
Foo<StringWrapper> foo = new FooBuilder<StringWrapper>()
.parent(new StringWrapperBuilder().value("PARENT").build())
.children(Arrays.asList(new StringWrapperBuilder().value("CHILD1").build()))
.build();
json = mapper.writeValueAsString(foo);
System.out.println(json);
System.out.println(mapper.readValue(json, Foo.class));
}
}
#AutoMatter
interface Foo<T> {
Optional<T> parent();
Optional<List<T>> children();
}
#AutoMatter
interface StringWrapper {
String value();
}
Above code prints:
Foo{parent=Optional[Aaaa], children=Optional[[a]]}
{
"parent" : {
"value" : "PARENT"
},
"children" : [ {
"value" : "CHILD1"
} ]
}
Foo{parent=Optional[{value=PARENT}], children=Optional[[{value=CHILD1}]]}
For now I just had to abandon parameterizing. I only had so many classes,
Foo<A>
Foo<B>
Foo<C>
It was easier to just create each one explicitly:
#AutoMatter
public interface FooA {
A parent;
List<A> children;
}
#AutoMatter
public interface FooB {
B parent;
List<B> children;
}
#AutoMatter
public interface FooC {
C parent;
List<C> children;
}
(I've also realized the Optionals were unnecessary.)
Not really an answer, so not accepting it.

Java Jackson 2.8.3 serializing list containing abstract objects with circular dependencies

I try to serialize some data with jackson which works pretty well for most cases but now I have an issue with a list. The list is of type A which is an abstract class and may contain circular dependencies. I can't figure out how to serialize this construct with jackson. The combination of identityInformation and typeInformation doesn't seem to properly work.
Below is Examplecode which produces the issue I am facing.
I am using Jackson version 2.8.3. Am I missing something? Is there a good solution to serialize and deserialize this kind of list?
Thanks in advance for any help!
Code:
import java.util.ArrayList;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.CollectionType;
public class JacksonTest {
#JsonTypeInfo(
use = JsonTypeInfo.Id.CLASS,
include = JsonTypeInfo.As.PROPERTY,
property = "#class")
#JsonSubTypes({ #JsonSubTypes.Type(value = B.class) })
public static abstract class A {
public A member;
}
#JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class, property = "#id")
public static class B extends A {
}
public static void main(String[] args) {
List<A> list = new ArrayList<A>();
ObjectMapper mapper = new ObjectMapper();
B instance1 = new B();
B instance2 = new B();
instance1.member = instance2;
list.add(instance1);
list.add(instance2);
CollectionType listType = mapper.getTypeFactory().constructCollectionType(list.getClass(), A.class);
try{
String serialized = mapper.writerFor(listType).writeValueAsString(list);
System.out.println(serialized);
list = mapper.readValue(serialized, listType);
} catch (Exception e){
e.printStackTrace();
}
}
}
Generated json String:
[{"#class":"JacksonTest$B","#id":1,"member":{"#class":"JacksonTest$B","#id":2,"member":null}},2]
Error when trying to read the String:
com.fasterxml.jackson.databind.JsonMappingException: Unexpected token (VALUE_NUMBER_INT), expected FIELD_NAME: missing property '#class' that is to contain type id (for class JacksonTest$A)
It seems like jackson expects the second entry to also be a json object containing the #class field and doesn't recognize that the second array element is a reference to an already existing object.
Type of field "A.member" is "A" and you missed out #JsonIdentityInfo for "A" type. Due to this, Jackson consider the object from list is a normal object( only id was serialized) and due to lack of identityinfo, jackson is not able to apply the object reference lookup mechanism during de-serializing and hence you see the error.
To fix your issue you need to add "#JsonIdentityInfo for type "A" too or better you can move #JsonIdentityInfo declaration from type "B" to "A".
code:
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.sameInstance;
import static org.junit.Assert.*;
import java.util.ArrayList;
import java.util.List;
import org.hamcrest.core.IsSame;
import org.junit.Test;
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.CollectionType;
public class JacksonTest {
#JsonTypeInfo(
use = JsonTypeInfo.Id.CLASS,
include = JsonTypeInfo.As.PROPERTY,
property = "#class")
#JsonSubTypes({ #JsonSubTypes.Type(value = B.class) })
#JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class, property = "#id")
public static abstract class A {
public A member;
}
public static class B extends A {
}
#Test
public void testCirularPolymorphicSerialization() throws Exception {
ObjectMapper mapper = new ObjectMapper();
// set up fixture
List<A> list = new ArrayList<A>();
B instance1 = new B();
B instance2 = new B();
instance1.member = instance2;
list.add(instance1);
list.add(instance2);
CollectionType listType = mapper.getTypeFactory().constructCollectionType(list.getClass(), A.class);
String serialized = mapper.writerFor(listType).writeValueAsString(list);
list = mapper.readValue(serialized, listType);
assertThat(list.size(), is(2));
assertThat(list.get(0).member, sameInstance(list.get(1)));
}
}

How to make polymorphic jackson serialization working with map

I try to have Jackson property type info serialized, even when my type is referenced by a map.
With this simple sample:
import java.util.HashMap;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class DemoJackson {
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY)
public static abstract class Animal {}
#JsonTypeName("cat")
public static class Cat extends Animal {}
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
Cat cat = new Cat();
System.out.println(objectMapper.writeValueAsString(cat));
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("data", cat);
System.out.println(objectMapper.writeValueAsString(map));
}
}
I get:
{"#type":"cat"}
{"data":{}}
But I would like to have:
{"#type":"cat"}
{"data":{"#type":"cat"}}
Is it possible? How to do it?
I have tried with enableDefaultTyping but I get:
{"#type":"cat"}
{"data":["DemoJackson$Cat",{}]}
When you are serializing a map directly, as you do, the object mapper needs the type information about the map contents because of generic typing. Please refer to chapter 5.1 of Polymorphic Type Handling wiki page.
An example of passing the type reference information when serializing the map of Animals.
objectMapper.writerWithType(new TypeReference<Map<String, Animal>>() {})
.writeValueAsString(map)
Output:
{"#type":"cat"}
{"data":{"#type":"cat"}}

Categories