Reputation: 43
I will two different types of in separate calls. I want to deserialize or serialize depending on the business requirement.
one type of json:
{
"type": "foo",
"data": [{
"someCommonProperty": "common property",
"fooProperty": "foo specific property"
},{
"someCommonProperty": "common property1",
"fooProperty": "foo specific property1"
}]
}
Another type of json:
{
"type": "bar",
"data": [{
"someCommonProperty": "common property",
"barProperty": "bar specific property",
"type": "type1"
},{
"someCommonProperty": "common property1",
"barProperty": "bar specific property1",
"type": "type1"
}]
}
I have classes as below
public class Parent {
private String type;
@JsonTypeInfo(use = Id.NAME, property = "type", include = As.EXTERNAL_PROPERTY)
@JsonSubTypes(value = {
@JsonSubTypes.Type(value = Foo.class, name = "foo"),
@JsonSubTypes.Type(value = Bar.class, name = "bar")
})
private List<AbstractData> data;
// Getters and setters
}
public abstract class AbstractData {
private String someCommonProperty;
// Getters and setters
}
public class Foo extends AbstractData {
private String fooProperty;
// Getters and setters
}
public class Bar extends AbstractData {
private String barProperty;
private String type;
// Getters and setters
}
When I try to deserialize as below, I am getting empty string on writing out java object as json.
ObjectMapper mapper = new ObjectMapper();
Parent parent = mapper.readValue(json, Parent.class);
I get this error:
com.fasterxml.jackson.databind.JsonMappingException: Unexpected token (START_OBJECT), expected START_ARRAY: need JSON Array to contain As.WRAPPER_ARRAY type information for class com.DemoJsonToJavaDeserialize.AbstractData
at [Source: C:<projectpath>\target\classes\foo.json; line: 3, column: 12] (through reference chain: com.DemoJsonToJavaDeserialize.Parent["data"]->java.util.ArrayList[0])
Upvotes: 3
Views: 4587
Reputation: 261
If you have access to the AbstractData
class, you might want to use the new 2.12 Jackson Deduction feature :
@JsonSubTypes(value = {
@JsonSubTypes.Type(value = Foo.class),
@JsonSubTypes.Type(value = Bar.class)
})
@JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION)
public abstract class AbstractData {
private String someCommonProperty;
// getters setters
}
Then, no need to annotate Foo
, Bar
and Parent
: everything will get instantiated automagically as long as your subclasses have different property names. type
json property becomes useless as well.
(If you are using SpringBoot, you can set the jackson-bom.version
property to upgrade jackson inside spring boot)
the javadoc of JsonTypeInfo.As.EXTERNAL_PROPERTY
says : Note also that this mechanism can not be used for container values (arrays, Collections...
. I believe this is your problem: you're applying it to a List
.
If you can't modify any of the Foo/Barr/AbstractData classes, the classic way is to use a custom Deserializer.
On the Parent
class :
@JsonDeserialize(contentUsing = FooBarDeserializer.class)
private List<AbstractData> data;
And the Deserializer:
public class FooBarDeserializer extends JsonDeserializer<AbstractData> {
@Override
public AbstractData deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException {
JsonNode node = jsonParser.readValueAsTree();
if(node.has("fooProperty"))
return jsonParser.getCodec().treeToValue(node,Foo.class);
else
return jsonParser.getCodec().treeToValue(node,Bar.class);
}
}
If you find a solution that make use of your Parent.type
, please share, I'm interested.
Upvotes: 8