Using Jackson to / from JSON where each class implements the same interface - java

I'm trying to deserialize a fairly complex JSON structure using Jackson.
Serializing works fine:
Param interface:
public interface Param<T> {
}
Not.java:
public class Not implements Param<Param<?>> {
private Param not;
public Not(){
}
public Not(Param not) {
this.not = not;
}
public Param getNot() {
return not;
}
}
And.java:
public class And implements Param<List<?>> {
private List<Param> and;
public List<Param> getAnd() {
return and;
}
public List<Param> add(Param ... params){
for (Param param : params){
this.and.add(param);
}
return this.and;
}
public And() {
this.and = new ArrayList<>();
}
public And(Param ... params){
this.and = new ArrayList<>();
for (Param param : params){
this.and.add(param);
}
}
}
Company.java:
public class CompanyName implements Param<String> {
private String companyName;
public CompanyName(String value) {
this.companyName = value;
}
public String getCompanyName() {
return companyName;
}
}
Serializing:
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.writeValueAsString(new And(new Or(new CompanyName("ABC"), new CompanyName("DEF")), new Not(new CompanyName("GHI")))));
Prints:
{"and":[{"or":[{"companyName":"ABC"},{"companyName":"DEF"}]},{"not":{"companyName":"GHI"}}]}
Now deserializing, how does Jackson know how to map and / or / companyName / not back to their objects?
And and = mapper.readValue(json, And.class);
Exception in thread "main"
com.fasterxml.jackson.databind.JsonMappingException: Can not construct
instance of com.ahp.messaging.param.Param, problem: abstract types
either need to be mapped to concrete types, have custom deserializer,
or be instantiated with additional type information at [Source:
{"and":[{"or":[{"companyName":"ABC"},{"companyName":"DEF"}]},{"not":{"companyName":"GHI"}}]};
line: 1, column: 9] (through reference chain:
com.ahp.messaging.param.And["and"]->java.util.ArrayList[0])
To get rid of the exception, I modified the Param interface as follow:
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "T", visible = false)
#JsonSubTypes({
#JsonSubTypes.Type(value = Not.class, name = "not"),
#JsonSubTypes.Type(value = And.class, name = "and"),
#JsonSubTypes.Type(value = Or.class, name = "or"),
#JsonSubTypes.Type(value = CompanyName.class, name = "companyName")
})
public interface Param<T> {
}
Now it serializes to this:
{"T":"and","and":[{"T":"or","or":[{"T":"companyName","companyName":"ABC"},{"T":"companyName","companyName":"DEF"}]},{"T":"not","not":{"T":"companyName","companyName":"GHI"}}]}
which deserializes perfectly, but there's type information on everything, is there a way to get rid of the type information and only have it where it's really needed?

Short answer, the type information is needed.
With XML you get the type information with every element, with JSON, you sacrifice the type information for smaller representations, but in this instance, scrapping the type information means there's no way to infer what class it should map to.

Related

com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "__type"

I'm trying to understand how to do JSON deserialization using Jackson.
My JSON looks like this:
{
"__type": "base",
"__ConfigAType": "Sona",
"sonaString": "some test string",
"allRequiredSchemas": "[\"abc\"]",
"stepName": "GL1",
"preReq": []
}
My classes look like this:
public class Step {
private String stepName;
private List<Step> preReq;
private ModuleConfig moduleConfig;
}
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "__type")
#JsonSubTypes({
#JsonSubTypes.Type(value = BaseConfig.class, name = BaseConfig.TYPE)
})
public interface ModuleConfig {
}
public class BaseConfig implements ModuleConfig {
public static final String TYPE = "base";
}
#JsonAutoDetect(getterVisibility = JsonAutoDetect.Visibility.PUBLIC_ONLY)
#JsonTypeName(SonaConfig.TYPE)
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "__ConfigAType")
#JsonSubTypes({
#JsonSubTypes.Type(value = SonaConfig.class, name = SonaConfig.TYPE)
})
public abstract class ConfigA extends BaseConfig {
public static final String TYPE = "ConfigA";
private List<String> allRequiredSchemas;
abstract Boolean execute(String input);
abstract String getConfigAType();
}
public class SonaConfig extends ConfigA {
public static final String TYPE = "SONA";
private String sonaString;
#Override Boolean execute(String input) {
return true;
}
#Override String getConfigAType() {
return "Sona";
}
}
When I try to deserialize the JSON into a Step Class I get the following error:
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "__type"
I'm parsing it the following way:
Step x = new ObjectMapper().readerFor(Step.class).readValue(json);
Because the field preReq will contain a list of objects of type Step, I would ideally like it to reference another JSON blob instead of creating one giant JSON. Hence I've a class of type BaseConfig and my plan is to have a RefConfig which can read references to other JSON files.

How to deserialize JsonString using ObjectMapper with Generic Type of a class

Let's say If I have a model class ResponseModel
#Setter // This one not working
public class ResponseModel<T> {
private Class<T> responseClass;
private String content; // JsonString
public <T> T getContent() throws IOException {
ObjectMapper mapper = new ObjectMapper();
return mapper.readValue(content, responseClass);
}
}
What I want to do is I want to pass Generic Type to the class ResponseModel and when I call method getContent() it should return the mapped object according to responseClass
Here is an example that I want to do it
// Color Pojo
#Data
public class Color {
private String nameValue;
private String hexValue;
}
// prepare mocked content
final String content = "{\n" +
"\"nameValue\":\"red\",\n" +
"\"hexValue\":\"FFFFFF\"\n" +
"}";
// Declare ResponseModel Object
ResponseModel<Color> response = new ResponseModel<>();
response.setContent(content);
response.getContent().getNameValue(); // should return red
response.getContent().getHexValue(); // should return FFFFFF
anyone knows how to do this?
You can make a static method to deserialize an object with generic type
public class MyDeserializer {
private static final ObjectMapper objectMapper = new ObjectMapper();
public static <T> T convertValue(String content, Class<T> contentClass) throws IOException {
Assert.notNull(content, "Content cannot be null");
Assert.notNull(contentClass, "Content class must be specified");
return objectMapper.readValue(content, contentClass);
}
}
and to test your method :
Color color = MyDeserializer.convertValue("{" +
"\"nameValue\":\"red\"," +
"\"hexValue\":\"FFFFFF\"" + "}", Color.class);
assertEquals("red", color.getNameValue());
assertEquals("FFFFFF", color.getHexValue());
In this way, you can use the deserializer for any class at runtime.
UPDATE
To make your example work you need to remove before getContent to match with generic type T from class.
public T getContent() throws IOException {
ObjectMapper mapper = new ObjectMapper();
return mapper.readValue(content, responseClass);
}
and to use your method :
responseModel.setContent("{" +
"\"nameValue\":\"red\"," +
"\"hexValue\":\"FFFFFF\"" + "}");
responseModel.setResponseClass(Color.class);
At runtime the generic type is replaced with Object, so you must specify the expected class.
I still consider that the first solution is the clean one. You are asking if it is possible to deduce T class, but it is not possible to see the type of T at runtime.
You can write an interface with JsonSubTypes to be automatically deserialized as follows:
#JsonIgnoreProperties(value = {"type"})
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = Subclass1.class, name = "SUBCLASS1"),
#JsonSubTypes.Type(value = Subclass2.class, name = "SUBCLASS2")
})
public interface DeserializableModelInterface {
}
and write your ResponseModel with this specific object
public class ResponseModel<T extends AbstractDeserializer> {
private T body;
public ResponseModel(T body) {
this.body = body;
}
public T getBody() {
return body;
}
}
Your body is a specific object that you can get at runtime without converting json explicitly
In your Subclass1, Subclass2 and so on, you will have an additional json property that will permit to jackson to de/serialize automatically
{
"type": "SUBCLASS1"
... other properties
}
To Avoid manual mapping for subclass value, you can use class name for example:
#JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "class")

Jackson deserialize based on type

Lets say I have JSON of the following format:
{
"type" : "Foo"
"data" : {
"object" : {
"id" : "1"
"fizz" : "bizz"
...
},
"metadata" : {
...
},
"owner" : {
"name" : "John"
...
}
}
}
I am trying to avoid custom deserializer and attempting to deserialize the above JSON (called Wrapper.java) into Java POJOs. The "type" field dictates the "object" deserialization ie. type = foo means the deserialize the "object" field using the Foo.java. (if type = Bar, use Bar.java to deserialize the object field). Metadata/owner will always deserialize the same way using a simple Jackson annotated Java class for each. Is there a way to accomplish this using annotations? If not, how can this be done using a custom deserializer?
Annotations-only approach
Alternatively to the custom deserializer approach, you can have the following for an annotations-only solution (similar to the one described in Spunc's answer, but using type as an external property):
public abstract class AbstractData {
private Owner owner;
private Metadata metadata;
// Getters and setters
}
public static final class FooData extends AbstractData {
private Foo object;
// Getters and setters
}
public static final class BarData extends AbstractData {
private Bar object;
// Getters and setters
}
public class Wrapper {
private String type;
#JsonTypeInfo(use = Id.NAME, property = "type", include = As.EXTERNAL_PROPERTY)
#JsonSubTypes(value = {
#JsonSubTypes.Type(value = FooData.class, name = "Foo"),
#JsonSubTypes.Type(value = BarData.class, name = "Bar")
})
private AbstractData data;
// Getters and setters
}
In this approach, #JsonTypeInfo is set to use type as an external property to determine the right class to map the data property.
The JSON document can be deserialized as following:
ObjectMapper mapper = new ObjectMapper();
Wrapper wrapper = mapper.readValue(json, Wrapper.class);
Custom deserializer approach
You could use a custom deserializer that checks the type property to parse the object property into the most suitable class.
First define an interface that will be implemented by Foo and Bar classes:
public interface Model {
}
public class Foo implements Model {
// Fields, getters and setters
}
public class Bar implements Model {
// Fields, getters and setters
}
Then define your Wrapper and Data classes:
public class Wrapper {
private String type;
private Data data;
// Getters and setters
}
public class Data {
#JsonDeserialize(using = ModelDeserializer.class)
private Model object;
private Metadata metadata;
private Owner owner;
// Getters and setters
}
The object field is annotated with #JsonDeserialize, indicating the deserializer that will be used for the object property.
The deserializer is defined as following:
public class ModelDeserializer extends JsonDeserializer<Model> {
#Override
public Model deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonMappingException {
// Get reference to ObjectCodec
ObjectCodec codec = jp.getCodec();
// Parse "object" node into Jackson's tree model
JsonNode node = codec.readTree(jp);
// Get value of the "type" property
String type = ((Wrapper) jp.getParsingContext().getParent()
.getCurrentValue()).getType();
// Check the "type" property and map "object" to the suitable class
switch (type) {
case "Foo":
return codec.treeToValue(node, Foo.class);
case "Bar":
return codec.treeToValue(node, Bar.class);
default:
throw new JsonMappingException(jp,
"Invalid value for the \"type\" property");
}
}
}
The JSON document can be deserialized as following:
ObjectMapper mapper = new ObjectMapper();
Wrapper wrapper = mapper.readValue(json, Wrapper.class);
Alternatively to this custom deserializer, consider an annotations-only approach.
All this can be done by means of annotations.
Create an abstract superclass with the common fields like "metadata" and "owner" and their getters/setters. This class needs to be annotated with #JsonTypeInfo. It should look like:
#JsonTypeInfo(use = Id.CLASS, include = As.PROPERTY, property = "type")
With the parameter property = "type" you specify that the class identifier will be serialized under the field type in your JSON document.
The value of the class identifier can be specified with use. Id.CLASS uses the fully-qualified Java class name. You can also use Id.MINIMAL_CLASS which is an abbreviated Java class name. To have your own identifier, use Id.NAME. In this case, you need to declare the subtypes:
#JsonTypeInfo(use = Id.NAME, include = As.PROPERTY, property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = Foo.class, name = "Foo"),
#JsonSubTypes.Type(value = Bar.class, name = "Bar")
})
Implement your classes Foo and Bar by extending from the abstract superclass.
Jackson's ObjectMapper will use the additional field "type" of the JSON document for serialization and deserialization. E. g. when you deserialise a JSON string into a super class reference, it will be of the appropriate subclass:
ObjectMapper om = new ObjectMapper();
AbstractBase x = om.readValue(json, AbstractBase.class);
// x will be instanceof Foo or Bar
Complete code example (I used public fields as shortcut to not need to write getters/setters):
package test;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
import java.io.IOException;
import com.fasterxml.jackson.annotation.JsonSubTypes;
#JsonTypeInfo(use = Id.NAME, include = As.PROPERTY, property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = Foo.class, name = "Foo"),
#JsonSubTypes.Type(value = Bar.class, name = "Bar")
})
public abstract class AbstractBase {
public MetaData metaData;
public Owner owner;
#Override
public String toString() {
return "metaData=" + metaData + "; owner=" + owner;
}
public static void main(String[] args) throws IOException {
// Common fields
Owner owner = new Owner();
owner.name = "Richard";
MetaData metaData = new MetaData();
metaData.data = "Some data";
// Foo
Foo foo = new Foo();
foo.owner = owner;
foo.metaData = metaData;
CustomObject customObject = new CustomObject();
customObject.id = 20l;
customObject.fizz = "Example";
Data data = new Data();
data.object = customObject;
foo.data = data;
System.out.println("Foo: " + foo);
// Bar
Bar bar = new Bar();
bar.owner = owner;
bar.metaData = metaData;
bar.data = "A String in Bar";
ObjectMapper om = new ObjectMapper();
// Test Foo:
String foojson = om.writeValueAsString(foo);
System.out.println(foojson);
AbstractBase fooDeserialised = om.readValue(foojson, AbstractBase.class);
System.out.println(fooDeserialised);
// Test Bar:
String barjson = om.writeValueAsString(bar);
System.out.println(barjson);
AbstractBase barDeserialised = om.readValue(barjson, AbstractBase.class);
System.out.println(barDeserialised);
}
}
class Foo extends AbstractBase {
public Data data;
#Override
public String toString() {
return "Foo[" + super.toString() + "; data=" + data + ']';
}
}
class Bar extends AbstractBase {
public String data;
public String toString() {
return "Bar[" + super.toString() + "; data=" + data + ']';
}
}
class Data {
public CustomObject object;
#Override
public String toString() {
return "Data[object=" + object + ']';
}
}
class CustomObject {
public long id;
public String fizz;
#Override
public String toString() {
return "CustomObject[id=" + id + "; fizz=" + fizz + ']';
}
}
class MetaData {
public String data;
#Override
public String toString() {
return "MetaData[data=" + data + ']';
}
}
class Owner {
public String name;
#Override
public String toString() {
return "Owner[name=" + name + ']';
}
}
I think it is rather straight-forward. You probably have a super class that has properties for metadata and owner, so rather than making it truly generic, you could substitute T for your super class. But basically, you will have to parse the name of the class from the actual JSON string, which in your example would look something like this:
int start = jsonString.indexOf("type");
int end = jsonString.indexOf("data");
Class actualClass = Class.forName(jsonString.substring(start + 4, end - 2)); // that of course, is approximate - based on how you format JSON
and overall code could be something like this:
public static <T> T deserialize(String xml, Object obj)
throws JAXBException {
T result = null;
try {
int start = jsonString.indexOf("type");
int end = jsonString.indexOf("data");
Class actualClass = Class.forName(jsonString.substring(start + 4, end - 2));
JAXBContextFactory factory = JAXBContextFactory.getInstance();
JAXBContext jaxbContext = factory.getJaxBContext(actualClass);
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
// this will create Java object
try (StringReader reader = new StringReader(xml)) {
result = (T) jaxbUnmarshaller.unmarshal(reader);
}
} catch (JAXBException e) {
log.error(String
.format("Exception while deserialising the object[JAXBException] %s\n\r%s",
e.getMessage()));
}
return result;
}

Jackson Polymorphic Deserialization expected START_ARRAY

I need to deserializa a Json like this:
{
"arrayObj1":[
{
"type":"t1",
"value":[
{
"value1":"a"
},
{
"value2":"b"
}
],
"otherInfo":"abc"
}
]
}
Thats how i try to do it:
public class ClassA{
private ArrayObj1[] arrayObj1;}
Then...
public class ArrayObj1 extends Value{
private String type;
private Value [] value;
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXTERNAL_PROPERTY,
property = "type")
#JsonSubTypes(value={
#JsonSubTypes.Type(value = T1.class, name = "t1")
})
public void setValue (Value [] value){
this.value = value;
}
}
and...
public abstract class Value {}
This is the error iv got:
com.fasterxml.jackson.databind.JsonMappingException: Unexpected token (START_OBJECT), expected START_ARRAY: need JSON Array to contain As.WRAPPER_ARRAY type information for class ... etc
How to handle this? Thanks.
EDIT: This is how i deserialize:
public ClassA getObj (String jsonString) {
ClassA obj = null;
try {
obj = new ObjectMapper().readValue(jsonString, ClassA.class);
} catch (IOException e) {
e.printStackTrace();
}
return obj;
}
Full error is:
com.fasterxml.jackson.databind.JsonMappingException: Unexpected token
(START_OBJECT), expected START_ARRAY: need JSON Array to contain
As.WRAPPER_ARRAY type information for class .Value at [Source:
java.io.StringReader#4317b868; line: 1, column: 496] (through
reference chain: .ClassA["arrayObj1"]->.ArrayObj1["value"])
Given the structure you use, you actually need one more level of classes to contain external type id. So something like this:
public class ClassA {
private ValueWrapper[] arrayObj1;
}
public class ValueWrapper {
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXTERNAL_PROPERTY,
property = "type")
#JsonSubTypes(value={
#JsonSubTypes.Type(value = T1.class, name = "t1")
}
public Value value;
public String otherInfo;
// or use getters/setters in addition; left as fields for brevity
}
public abstract class Value { ... }
)
However, looking at your JSON more closely, this actually would only work if your values were POJOs. If assumption is that type is for all elements of the value array, there is no automated way to do that.
You will unfortunately need to handle polymorphic types manually.

What #JsonTypeInfo.ID to choose for property = "type.id" for deserialization, JsonTypeInfo.Id.CUSTOM?

So I have JSON that looks like this:
{
"ActivityDisplayModel" : {
"name" : "lunch with friends",
"startTime" : "12:00:00",
"type" : {
"id" : "MEAL",
"description" : "Meal"
},
"complete" : false
}
}
I'm trying to find the way to get #JsonTypeInfo to not be mad at me for having the type parameter inside the type object. I've got this working before when the field type was a String and not an object itself, but for later processing I need it as an object. I know the following doesn't work, and I'm guessing theres a way to use JsonTypeInfo.Id.CUSTOM, but after looking all over on the internet, no full examples with JSON have come up. Also, if this is possible with an objectMapper setting, I'm all ears.
/**
* My ActivityDisplayModel Abstract Class
*/
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type.id")
#JsonSubTypes({
#JsonSubTypes.Type(value = MealDisplayModel.class, name = "MEAL"),
#JsonSubTypes.Type(value = EntertainmentDisplayModel.class, name = "ENTERTAINMENT")
})
public abstract class ActivityDisplayModel {
...
The above is essentially what I want to do, but of course I get an exception of:
Could not read JSON: Could not resolve type id '{' into a subtype of [simple type, class ... .ActivityDisplayModel]
For such a simple problem of just looking one level deeper in the JSON, who would have thought it would have been so much trouble?
I know it's been 3 years since the original question, but dot-nested properties are still not supported and maybe this will help someone out. I ended up creating a class NestedTypeResolver so we can use the dot-syntax as expected. Simply add #JsonTypeResolver(NestedTypeResolver.class) to any class with nested discriminators and the poster's original attempt will work:
/**
* My ActivityDisplayModel Abstract Class
*/
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type.id")
#JsonSubTypes({
#JsonSubTypes.Type(value = MealDisplayModel.class, name = "MEAL"),
#JsonSubTypes.Type(value = EntertainmentDisplayModel.class, name = "ENTERTAINMENT")
})
#JsonTypeResolver(NestedTypeResolver.class)
public abstract class ActivityDisplayModel {
NestedTypeResolver:
/**
* Allows using nested "dot" dyntax for type discriminators. To use, annotate class with #JsonTypeResolver(NestedTypeResolver.class)
*/
public class NestedTypeResolver extends StdTypeResolverBuilder {
#Override
public TypeDeserializer buildTypeDeserializer(DeserializationConfig config, JavaType baseType,
Collection<NamedType> subtypes) {
//Copied this code from parent class, StdTypeResolverBuilder with same method name
TypeIdResolver idRes = idResolver(config, baseType, subtypes, false, true);
return new NestedTypeDeserializer(baseType, idRes, _typeProperty, _typeIdVisible,
null, _includeAs);
}
}
All the heavy work is done in here, NestedTypeDeserializer:
/**
* Heavy work to support {#link NestedTypeResolver}
*/
public class NestedTypeDeserializer extends AsPropertyTypeDeserializer {
private static final Logger LOGGER = LoggerFactory.getLogger(NestedTypeDeserializer.class);
public NestedTypeDeserializer(JavaType bt,
TypeIdResolver idRes, String typePropertyName, boolean typeIdVisible,
JavaType defaultImpl) {
super(bt, idRes, typePropertyName, typeIdVisible, defaultImpl);
}
public NestedTypeDeserializer(JavaType bt, TypeIdResolver idRes, String typePropertyName, boolean typeIdVisible,
JavaType defaultImpl, JsonTypeInfo.As inclusion) {
super(bt, idRes, typePropertyName, typeIdVisible, defaultImpl, inclusion);
}
public NestedTypeDeserializer(AsPropertyTypeDeserializer src, BeanProperty property) {
super(src, property);
}
#Override
public TypeDeserializer forProperty(BeanProperty prop) {
return (prop == _property) ? this : new NestedTypeDeserializer(this, prop);
}
#Override
public Object deserializeTypedFromObject(JsonParser p, DeserializationContext ctxt) throws IOException {
JsonNode originalNode = p.readValueAsTree();
JsonNode node = originalNode;
//_typePropertyName is the dot separated value of "property" in #JsonTypeInfo
LOGGER.debug("Searching for type discriminator [{}]...", _typePropertyName);
for (String property : _typePropertyName.split("\\.")) { //traverse down any nested properties
JsonNode nestedProp = node.get(property);
if (nestedProp == null) {
ctxt.reportWrongTokenException(p, JsonToken.FIELD_NAME,
"missing property '" + _typePropertyName + "' that is to contain type id (for class "
+ baseTypeName() + ")");
return null;
}
node = nestedProp;
}
LOGGER.debug("Found [{}] with value [{}]", _typePropertyName, node.asText());
JsonDeserializer<Object> deser = _findDeserializer(ctxt, "" + node.asText());
//Since JsonParser is a forward-only operation and finding the "type" discriminator advanced the pointer, we need to reset it
//Got clues from https://www.dilipkumarg.com/dynamic-polymorphic-type-handling-jackson/
JsonParser jsonParser = new TreeTraversingParser(originalNode, p.getCodec());
if (jsonParser.getCurrentToken() == null) {
jsonParser.nextToken();
}
return deser.deserialize(jsonParser, ctxt);
}
}
Disclaimer: we've been using this for a month with Jackson 2.8.10 and have had no issues, but we had to go deep into the Jackson source code weeds to accomplish it, so YMMV.
Hopefully Jackson will allow this out-of-the-box someday so we dont need these workarounds.
I am not sure that you can do it with specifying inner property: type.id. In my opinion you should change your JSON to simpler version. If you can not force your JSON supplier to change JSON schema you have to do it manually. Assume that your JSON looks like below:
{
"activityDisplayModel": {
"name": "lunch with friends",
"type": {
"id": "MEAL",
"description": "Meal"
},
"complete": false
}
}
Below POJO classes fit to above JSON:
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = MealDisplayModel.class, name = "MEAL"),
#JsonSubTypes.Type(value = EntertainmentDisplayModel.class, name = "ENTERTAINMENT")
})
abstract class ActivityDisplayModel {
protected String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Override
public String toString() {
return name;
}
}
class MealDisplayModel extends ActivityDisplayModel {
private boolean complete;
public boolean isComplete() {
return complete;
}
public void setComplete(boolean complete) {
this.complete = complete;
}
#Override
public String toString() {
return "MealDisplayModel [complete=" + complete + ", toString()=" + super.toString() + "]";
}
}
#JsonIgnoreProperties("complete")
class EntertainmentDisplayModel extends ActivityDisplayModel {
#Override
public String toString() {
return "EntertainmentDisplayModel [toString()=" + super.toString() + "]";
}
}
class Root {
private ActivityDisplayModel activityDisplayModel;
public ActivityDisplayModel getActivityDisplayModel() {
return activityDisplayModel;
}
public void setActivityDisplayModel(ActivityDisplayModel activityDisplayModel) {
this.activityDisplayModel = activityDisplayModel;
}
#Override
public String toString() {
return activityDisplayModel.toString();
}
}
Below script shows how you can parse above JSON:
ObjectMapper mapper = new ObjectMapper();
// Updated JSON in memory
ObjectNode rootNode = (ObjectNode)mapper.readTree(json);
ObjectNode activityDisplayModelNode = (ObjectNode)rootNode.path("activityDisplayModel");
JsonNode typeNode = activityDisplayModelNode.path("type");
activityDisplayModelNode.set("type", typeNode.path("id"));
System.out.println("Result: " + mapper.convertValue(rootNode, Root.class));
Above script prints:
Result: MealDisplayModel [complete=false, toString()=lunch with friends]
Also see:
Jackson Tree Model Example.
Convert Java Object to JsonNode in Jackson.

Categories

Resources