I'm trying to do a simple example of deserializing json into polymorphic classes. The deserialization fails with the error:
org.codehaus.jackson.map.JsonMappingException: Could not resolve type id 'aField' into a subtype of [simple type, class ...SubClassA]
If I try the deserialization for a single subclass, just using the JSON for that class, it succeeds, but when I put the two classes together inside the SubClassTestObject, it fails. Any ideas for fixing this? Do I need to write a custom deserializer?
Here is my JSON:
{
"classA":{
"aField":"A",
"baseField":"baseA"
},
"classB":{
"baseField":"baseB",
"bField":"B"
}
}
Here are my classes:
#JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.WRAPPER_OBJECT)
#JsonSubTypes({
#JsonSubTypes.Type(value = SubClassA.class, name = "classA"),
#JsonSubTypes.Type(value = SubClassB.class, name = "classB")
})
#JsonIgnoreProperties(ignoreUnknown = true)
public abstract class AbstractSimpleClass {
String baseField;
public String getBaseField() {
return baseField;
}
public void setBaseField(String baseField) {
this.baseField = baseField;
}
}
public class SubClassA extends AbstractSimpleClass {
String aField;
public String getaField() {
return aField;
}
public void setaField(String aField) {
this.aField = aField;
}
}
public class SubClassB extends AbstractSimpleClass {
String bField;
public String getbField() {
return bField;
}
public void setbField(String bField) {
this.bField = bField;
}
}
#JsonIgnoreProperties(ignoreUnknown = true)
public class SubClassTestObject {
#JsonProperty("classA")
SubClassA a;
#JsonProperty("classB")
SubClassB b;
public SubClassA getA() {
return a;
}
public void setA(SubClassA a) {
this.a = a;
}
public SubClassB getB() {
return b;
}
public void setB(SubClassB b) {
this.b = b;
}
}
And here is my test:
#Test
public void testBoth() throws IOException, URISyntaxException {
ClassLoader classLoader = getClass().getClassLoader();
json = new String(Files.readAllBytes(Paths.get(classLoader.getResource("test/so-example.json").toURI())));
ObjectMapper mapper = new ObjectMapper();
mapper.registerSubtypes(SubClassA.class, SubClassB.class);
SubClassTestObject testObj = mapper.readValue(json, SubClassTestObject.class); //Fails here
SubClassA a = testObj.getA();
SubClassB b = testObj.getB();
assertTrue(a.getBaseField().equals("baseA"));
assertTrue(b.getBaseField().equals("baseB"));
assertTrue(a.getaField().equals("A"));
assertTrue(b.getbField().equals("B"));
}
After your edit:
This
#JsonProperty("classA")
SubClassA a;
is completely unrelated to
#JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.WRAPPER_OBJECT)
The JsonTypeInfo.As.WRAPPER_OBJECT is meant to be implicit. It's not something you map in your POJOs. You won't be able to map your current JSON with this strategy. Either do what's below or change your JsonTypeInfo to use a JsonTypeInfo.As.PROPERTY for example, then provide the corresponding #type (and its appropriate value) property in the JSON.
Pre-edit:
Your SubClassTestObject class has two properties, a and b, because of
public SubClassA getA() {
return a;
}
public SubClassB getB() {
return b;
}
These aren't present in your JSON. And since you told Jackson to ignore unknown properties, it doesn't fail to deserialize. However, both of them are going to be left uninitialized.
The JSON you meant to deserialize is
{
"a": {
"classA": {
"aField": "A",
"baseField": "baseA"
}
},
"b": {
"classB": {
"baseField": "baseB",
"bField": "B"
}
}
}
which has a and b for your root SubClassTestObject object. And those use wrapper objects with the appropriate JsonTypeInfo name.
Related
I have a base class (Foo) with 2 children (A and B). They look like this:
public abstract class Foo {
private String fooString;
public Foo(String fooString) {
this.fooString = fooString;
}
//getter
}
#JsonDeserialize(builder = A.ABuilder.class)
public class A extends Foo {
private int amount;
public A(String fooString, int amount) {
super(fooString);
this.amount = amount;
}
//getter
#JsonPOJOBuilder
public static class ABuilder {
private String fooString;
private int amount;
public ABuilder withFooString(final String fooString) {
this.fooString = fooString;
return this;
}
public ABuilder withAmount(final int amount) {
this.amount = amount;
return this;
}
public A build() {
return new A(fooString, amount);
}
}
}
#JsonDeserialize(builder = B.BBuilder.class)
public class B extends Foo {
private String type;
public B(String fooString, String type) {
super(fooString);
this.type = type;
}
//getter
#JsonPOJOBuilder
public static class BBuilder {
private String fooString;
private String type;
public BBuilder withFooString(final String fooString) {
this.fooString = fooString;
return this;
}
public BBuilder withType(final String type) {
this.type = type;
return this;
}
public B build() {
return new B(fooString, type);
}
}
}
In my controller I have this endpoint:
#PutMapping
private ResponseEntity<Foo> doSomething(#RequestBody Foo dto) {
//stuff
}
But whenever I try to send over my json payload:
{
"fooString":"test",
"amount":1
}
I get the error:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.test.Foo` (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
at [Source: (String)"{"fooString":"test","amount":1}; line: 1, column: 1]
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)
at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1451)
at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1027)
at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserialize(AbstractDeserializer.java:265)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4013)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3004)
at AbstractJackson.main(AbstractJackson.java:11)
How do I get jackson to deserialize the json into the proper child class? What am I doing wrong?
The base class won't get the constructors of the sub classes instead it is quite the opposite,you cannot set subclass specific properties in base class instead you need to use specific subclass for the call or use custom deserializer for base class with correct use intanceOf
The easiest way to get it working is to change the controller method.
#PutMapping
private ResponseEntity<Foo> doSomething(#RequestBody A dto) {
//stuff
}
I use fasterxml to serialize/deserialize JSON
public class A {
String field;
B b;
}
public class B {
int n;
}
I want to get a JSON in format like this
{
"field": "abc",
"n": 123
}
Is it possible?
You can use Jackson annotation to provide a specific deserializer.
#JsonDeserialize(using = ADeserializer.class)
public class A {
private String field;
private B b;
// ...
}
A deserializer for your type should be like this
public class ADeserializer extends JsonDeserializer<A> {
#Override
public A deserialize(JsonParser p, DeserializationContext ctx) throws IOException {
ObjectCodec codec = p.getCodec();
JsonNode node = codec.readTree(p);
String field = node.get("field").asText();
int n = node.get("n").asInt();
A a = new A();
B b = new B();
b.setN(n);
a.setField(field);
a.setB(b);
return a;
}
}
For serialization it's possible to use custom Serializer. That's it.
You can simply use #JsonUnwrapped. No custom serializers are needed:
public class A {
public String field;
#JsonUnwrapped
public B b;
}
public class B {
public int n;
}
Pay attention to the fields accessibility or it will not work.
There is no way to do that in Java.
As of jackson-2.6, required property is only used for #JsonCreator. I have two class A and B. A has a required property and B is inherited from A.
When we use #JsonCreator, we can't get properties information from super class.See the following code, B doesn't check required property 'a'.
If we have many required properties, how to inherit? I don't want to repeat to write #JsonProperty.
public class App {
public static void main(String[] args) {
ObjectMapper objMapper = new ObjectMapper();
String jsonA = "{}"; // miss 'a'
try {
objMapper.readValue(jsonA, A.class);
} catch (IOException e) {
System.out.println("A: Should get exception"); // happen
e.printStackTrace();
}
String jsonB = "{\"b\":\"B\"}"; // miss 'a'
try {
objMapper.readValue(jsonB, B.class);
} catch (IOException e) {
System.out.println("B: Should get exception"); // not happen
e.printStackTrace();
}
}
}
class A {
private String a;
public A() {
}
#JsonCreator
public A(#JsonProperty(value = "a", required = true) String a) {
this.a = a;
}
public String getA() {
return a;
}
}
class B extends A {
private String b;
#JsonCreator
public B(#JsonProperty(value = "b", required = true) String b) {
this.b = b;
}
public String getB() {
return b;
}
}
Constructors are not inherited. in your example, B is calling parameter-less constructor A(). Note when you remove A() from A class, solution will not compile.
In your case, when you need class B to have property a and b you need to have that properties as constructor parameters for B
#JsonCreator
public B(#JsonProperty(value = "a", required = true) String a,
#JsonProperty(value = "b", required = true) String b,
) {
super(a);
this.b = b;
}
You may reuse parametrized constructor A(String a) by calling it from B(String a, String b).
You may also not use constructor and annotate fields a and b as #JsonProperty.
Then it should work without any constructors or any additional code.
i have a question concerning Json deserialization with Jackson (edit: 2.0.4 version). I would like serialize an Bean, containing the list of other beans, as string , save this string and then to deserialize later this string. I use the some base class and its subtypes. The basis class Parent is an abstract class, that has two attributes with getters und setters, this class has also an abstract method getType(). Other abstract class AbstractChild inherits from class Parent . This class has attributes too and isExportEnabled() abstract method.
I have no problems, if this Bean will be serialized. I use the following annotation on the Parent class
*#JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS, include = JsonTypeInfo.As.PROPERTY, property = "#cls")*
The string will be generated.
But the desirialize failed: the exception “Unrecognized field "type"”will be thrown. But I need this attribute! I’m tried to set #JsonProperty("type") on abstract method, this has no effect.
Please help me.
edit: if i introduce the private fields "type" (Parent) and "exportEnabled" (AbstractChild) so it runs correctly.
P.S The exception
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException:
Unrecognized field "type" (class tst.SimpleTestMain$FirstChild), not
marked as ignorable (4 known properties: , "id", "maxCount", "code",
"minCount"]) at [Source: java.io.StringReader#1ad9fa; line: 1,
column: 125] (through reference chain:
tst.Fam["members"]->tst.FirstChild["type"]) at
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException.from(UnrecognizedPropertyException.java:79)
at
com.fasterxml.jackson.databind.DeserializationContext.reportUnknownProperty(DeserializationContext.java:568)
…and the example class
package tst;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.ObjectMapper;
public class SimpleTestMain {
enum Type {
TYPE_A, TYPE_B
}
#JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS, include = JsonTypeInfo.As.PROPERTY, property = "#cls")
public static abstract class Parent {
private int id;
private String code;
public Parent() {
}
#JsonProperty("type")
// First abstract getter
public abstract Type getType();
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
}
public static abstract class AbstractChild extends Parent {
private int minCount;
private int maxCount;
public AbstractChild() {
}
// Second abstract method: boolean used
public abstract boolean isExportEnabled();
public int getMinCount() {
return minCount;
}
public void setMinCount(int minCount) {
this.minCount = minCount;
}
public int getMaxCount() {
return maxCount;
}
public void setMaxCount(int maxCount) {
this.maxCount = maxCount;
}
}
public static class FirstChild extends AbstractChild {
#Override
public boolean isExportEnabled() {
return false;
}
#Override
public Type getType() {
return Type.TYPE_A;
}
}
public static class SecondChild extends AbstractChild {
#Override
public boolean isExportEnabled() {
return true;
}
#Override
public Type getType() {
return Type.TYPE_B;
}
}
public static class Fam {
private int famId;
private List<Parent> members;
public Fam() {
members = new ArrayList<Parent>();
}
public int getFamId() {
return famId;
}
public void setFamId(int famId) {
this.famId = famId;
}
public List<Parent> getMembers() {
return members;
}
public void setMembers(List<Parent> members) {
this.members = members;
}
public void addMember(Parent member) {
members.add(member);
}
}
public SimpleTestMain() {
}
public static void main(String[] args) {
Fam fam = new Fam();
FirstChild fc = new FirstChild();
fc.setId(1);
fc.setCode("FirstChildCode");
fc.setMinCount(1);
fc.setMaxCount(4);
fam.addMember(fc);
SecondChild sc = new SecondChild();
sc.setCode("SecondChildCode");
sc.setMinCount(131);
sc.setMaxCount(431);
fam.addMember(sc);
String test = "";
// Serialize it
ObjectMapper mapper = new ObjectMapper();
try {
test = mapper.writeValueAsString(fam);
System.out.println("Serialized bean:\n" + test);
// the output
// Serialized bean:
// {"famId":0,"members":[{"#cls":".SimpleTestMain$FirstChild","id":1,"code":"FirstChildCode","minCount":1,"maxCount":4,"type":"TYPE_A","exportEnabled":false},{"#cls":".SimpleTestMain$SecondChild","id":0,"code":"SecondChildCode","minCount":131,"maxCount":431,"type":"TYPE_B","exportEnabled":true}]}
} catch (IOException e) {
e.printStackTrace();
}
// Deserialize it
mapper = new ObjectMapper();
// mapper.enableDefaultTyping();
try {
Fam fam1 = mapper.readValue(test, Fam.class);
} catch (IOException e) {
e.printStackTrace();
}
}
}
How do I programmatically set custom converter for dozer? The following code doesn't work:
Custom Converter implementation:
class ConverterImpl extends DozerConverter<A, B> {
ConverterImpl() {
super(A.class, B.class);
}
#Override
public B convertTo(A source, B destination) {
return destination;
}
#Override
public A convertFrom(B source, A destination) {
return destination;
}
}
Test code:
DozerBeanMapper mapper = new DozerBeanMapper();
mapper.setCustomConverters(Collections.<CustomConverter>singletonList(new ConverterImpl()));
A a = new A();
B b = mapper.map(a, A.class);
After running the code above, custom converter doesn't get invoked. What is wrong?
Looks like you have to actually add a specific mapping, and unfortunately you can only specify field-level converters, not class-level converters, using the programmatic API. So if you wrap the A and B classes in container classes, you can specify a mapping for the A and B fields.
For example the following verbose code works as expected:
public class DozerMap {
public static class ContainerA {
private A a;
public A getA() { return a; }
public void setA(A a) { this.a = a; }
}
public static class ContainerB {
private B b;
public B getB() { return b; }
public void setB(B b) { this.b = b; }
}
private static class A { };
private static class B { };
static class ConverterImpl extends DozerConverter<A, B> {
ConverterImpl() {
super(A.class, B.class);
}
#Override
public B convertTo(A source, B destination) {
Logger.getAnonymousLogger().info("Invoked");
return destination;
}
#Override
public A convertFrom(B source, A destination) {
Logger.getAnonymousLogger().info("Invoked");
return destination;
}
}
public static void main(String[] args) {
DozerBeanMapper mapper = new DozerBeanMapper();
mapper.setCustomConverters(Collections.<CustomConverter> singletonList(new ConverterImpl()));
BeanMappingBuilder foo = new BeanMappingBuilder() {
#Override
protected void configure() {
mapping(ContainerA.class, ContainerB.class).fields("a", "b", FieldsMappingOptions.customConverter(ConverterImpl.class));
}
};
mapper.setMappings(Collections.singletonList(foo));
ContainerA containerA = new ContainerA();
containerA.a = new A();
ContainerB containerB = mapper.map(containerA, ContainerB.class);
}
}
Why do you want to set it programatically ? I mean do you have any specific needs ? Otherwise, Mapping via an xml file works fine.
In case you want to do it more in a programing way , rather through some xml configuration files, Check out Orika.
It has good API support.