Jackson polymorphic deserialization - java

I've got the following problem with Jackson and type hierarchy. I'm serializing a class SubA which extends Base into a String,
and trying afterwards to derserialize it back. Of course at compile time, the system does not know whether it will be
Base or SubA so I'm expecting a Base and will do some other operations afterwards, if it is a SubA.
My Base class looks like:
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
#JsonSubTypes({
#Type(value = SubA.class, name = "SubA")
})
public class Base {
protected String command; // +get +set
protected String type; // +get +set
}
... and a class deriving from Base:
#JsonTypeName("SubA")
public class SubA extends Base {
private AnotherClass anotherClass; // +get +set
private String test; // +get +set
#JsonIgnore
#Override
public String getType() {
return "SubA";
}
}
... and I'm trying to execute the following code:
ObjectMapper mapper = new ObjectMapper();
ObjectWriter ow = mapper.writer().withDefaultPrettyPrinter();
Base payload = new SubA(); // + setting anotherClass as well as test variables
String requestStringSend = ow.writeValueAsString(payload);
System.out.println("Sending: " + requestStringSend);
Base received = mapper.readValue(requestStringSend, Base.class);
String requestStringReceived = ow.writeValueAsString(received);
System.out.println("Received: " + requestStringReceived);
The String requestStringSend is:
Sending: {
"command" : "myCommand",
"type" : "SubA",
"anotherClass" : {
"data" : "someData"
},
"test" : "test123"
}
But I'm keep getting the same error over and over again. The mapper does now know what to do with the anotherClass parameter - it does not exist in Base. But I thought the mapper will convert it into an SubA class?
Exception in thread "main" org.codehaus.jackson.map.exc.UnrecognizedPropertyException: Unrecognized field "anotherClass" (Class com.test.Base), not marked as ignorable
at [Source: java.io.StringReader#1256ea2; line: 4, column: 21] (through reference chain: com.test.Base["anotherClass"])
at org.codehaus.jackson.map.exc.UnrecognizedPropertyException.from(UnrecognizedPropertyException.java:53)
at org.codehaus.jackson.map.deser.StdDeserializationContext.unknownFieldException(StdDeserializationContext.java:267)
at org.codehaus.jackson.map.deser.std.StdDeserializer.reportUnknownProperty(StdDeserializer.java:649)
at org.codehaus.jackson.map.deser.std.StdDeserializer.handleUnknownProperty(StdDeserializer.java:635)
at org.codehaus.jackson.map.deser.BeanDeserializer.handleUnknownProperty(BeanDeserializer.java:1355)
at org.codehaus.jackson.map.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:717)
at org.codehaus.jackson.map.deser.BeanDeserializer.deserialize(BeanDeserializer.java:580)
at org.codehaus.jackson.map.ObjectMapper._readMapAndClose(ObjectMapper.java:2723)
at org.codehaus.jackson.map.ObjectMapper.readValue(ObjectMapper.java:1854)
at com.test.Foo.main(Foo.java:32)
I had a look at the following questions/resources:
Json deserialization into another class hierarchy using Jackson
http://wiki.fasterxml.com/JacksonPolymorphicDeserialization

Your code looks correct for the use case. One possible problem is that you could be accidentally using Jackson 2 annotations with Jackson 1 ObjectMapper (I can see latter is Jackson from package names in exception). Version of annotations and mapper must match; otherwise annotations will be ignored, and this would explain problems you are seeing.

Related

Deserialize WRAPPER_OBJECT key as property using Jackson

I'm trying to deserialize this json data into list of objects:
[{
"a": {
"commonField": 1,
"aField": "AAA"
}
}, {
"b": {
"commonField": 2,
"bField": "BBB"
}
}]
Each object may be one of several types having both common and unique fields. Information about exact shape of an object is stored in json as key in the wrapper object.
I created corresponding classes for every known shape (set of unique fields) extending class containing all common fields. Also, I added Jackson annotations to the classes to enable polymorphic deserialization. Simplified, resulting classes look like this:
#JsonTypeInfo(use = Id.NAME, include = As.WRAPPER_OBJECT, property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(KeyBasedSubTypeA.class),
#JsonSubTypes.Type(KeyBasedSubTypeB.class)
})
public abstract class KeyBasedSuperType {
public String type;
public int commonField;
}
#JsonTypeName("a")
public class KeyBasedSubTypeA extends KeyBasedSuperType {
public String aField;
}
#JsonTypeName("b")
public class KeyBasedSubTypeB extends KeyBasedSuperType {
public String bField;
}
With this setup Jackson works almost perfectly. It is able to choose correct subtype during deserialization and populate all the fields including common and unique. However, the type field is not updated by Jackson, the key value used for selecting subtype is not stored anywhere. In other words, the data is deserialized into following structure:
[KeyBasedSubTypeA { type=null; commonField=1; aField=AAA },
KeyBasedSubTypeB { type=null; commonField=2; bField=BBB }]
Note type field having null value. So, the question is - How can I make Jackson to store wrapper's key used for selecting subtype somewhere in resulting object?
Here is my JUnit test for the process
public class PolymorphicTest {
private static ObjectMapper mapper;
#BeforeClass
public static void init() {
mapper = new ObjectMapper();
}
#Test
public void testKeyDenominator() throws IOException {
TypeReference<List<KeyBasedSuperType>> dataShape =
new TypeReference<List<KeyBasedSuperType>>() {};
List<KeyBasedSuperType> result = mapper.readValue(
PolymorphicTest.class.getResourceAsStream("polymorphic-key.json"), dataShape);
assertEquals(2, result.size());
assertEquals(KeyBasedSubTypeA.class, result.get(0).getClass());
assertEquals(KeyBasedSubTypeB.class, result.get(1).getClass());
assertEquals(1, result.get(0).commonField);
assertEquals(2, result.get(1).commonField);
assertEquals("a", result.get(0).type); // <---- this line fails
assertEquals("b", result.get(1).type); // <---- this line fails
assertEquals("AAA", ((KeyBasedSubTypeA) result.get(0)).aField);
assertEquals("BBB", ((KeyBasedSubTypeB) result.get(1)).bField);
}
}
The solution actually was very close, just missed a tiny step forward. It is #JsonTypeInfo(visible=true) required to make Jackson handle type info as normal property.
#JsonTypeInfo(use = Id.NAME, include = As.WRAPPER_OBJECT, property = "type", visible = true)
#JsonSubTypes({
#JsonSubTypes.Type(KeyBasedSubTypeA.class),
#JsonSubTypes.Type(KeyBasedSubTypeB.class)
})
public abstract class KeyBasedSuperType {
public String type;
public int commonField;
}

Use root name as property in entity

I'm kind of new in jackson subject and I did not find any answer which would help me resolve the problem.
For a sec let's assume that I have this class:
public class Airport {
private String name;
private String code;
...
}
My json looks like this:
"XXX": {
"name": "SomeName",
}
I would like to force Jackson to put XXX (root of tree) into code property from the class. Standard way I use to create objects from JSON is using treeToValue:
ObjectMapper mapper = new ObjectMapper();
String airports = "above Json";
JsonNode airportsTree = mapper.readTree(airports.toString());
Airport airport = mapper.treeToValue(airportsTree, Airport.class);
However when I enable DeserializationFeature.UNWRAP_ROOT_VALUE I'm getting
JsonMappingException: Root name 'XXX' does not match expected ('JsonNode') for type [simple type, class com.fasterxml.jackson.databind.JsonNode]
You need put root name hint for jackson
#JsonRootName(value = "XXX")
public class Airport {
private String name;
private String code;
...
}
When you enable DeserializationFeature.UNWRAP_ROOT_VALUE it must works

Missing field when deserializing using Jackson (Polymorphic)

I am trying to create an java SDK for a front-end library that takes JSON input. Essentially, this SDK converts objects of certain type into JSON which is then consumed by that front-end library.
I am using jackson's polymorphic serialization/deserialization using its annotation system.
I have a base class A and 2 child classes B and C extending A. Class A has a type field, using which I decide what class (B or C) is to be used. The syntax looks something like this:
#JsonTypeInfo({
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXISTING_PROPERTY,
property= "type"
})
#JsonSubTypes({
#JsonSubTypes.Type(value = B.class, name = "b"),
#JsonSubTypes.Type(value = C.class, name = "c")
})
public class A {
private String type;
public void setType(String type){
this.type = type;
}
public String getType(){
return this.type;
}
}
public class B extends A {
}
public class C extends A {
}
So now, when I use Jackson's ObjectMapper's readValue function and read the stringified JSON and convert to class A, I get the correct instance of either class A or class B based on the value of the type variable. However, and here is the actual problem, when I try to use the function getType I always get null in those objects. I am not sure why jackson is not setting those values on the object.
String json = "{ type: 'b' }"; // example input json
ObjectMapper om = new ObjectMapper();
A a = om.readValue(json, A.class);
// a is actually an instance of class B
a.getType()// this is null
You need to add to #JsonTypeInfo the parameter visible = true to avoid to remove the type when deserializing.
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXISTING_PROPERTY,
property= "type",
visible = true
)
public abstract boolean visible() default false
Property that defines whether type identifier value will be passed as part of JSON stream to deserializer (true), or handled and
removed by TypeDeserializer (false). Property has no effect on
serialization. Default value is false, meaning that Jackson handles
and removes the type identifier from JSON content that is passed to
JsonDeserializer.

Deserializing Polymorphic Types with #JsonUnwrapped using Jackson

What I Want to Do
I want to use Jackson to deserialize a polymorphic type, using the standard #JsonTypeInfo annotation as follows:
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME,
include = As.EXISTING_PROPERTY,
property = "identifier")
#JsonSubTypes({#Type(value = A.class, name = "A"),
#Type(value = B.class, name = "B")})
abstract Class Base {}
Class A implements Base {
public String identifier = "A";
}
Class B implements Base {
public String identifier = "B";
}
Class Decorated {
public String decoration = "DECORATION";
#JsonUnwrapped
public Base base;
}
/*
Serialized instance of Decorated WITHOUT #JsonUnwrapped:
{
"decoration" : "DECORATION",
"base" : {
"identifier" : "A"
}
}
Serialized instance of Decorated WITH #JsonUnwrapped:
{
"decoration" : "DECORATION",
"identifier" : "A"
}
*/
Related post: Deserialize JSON with Jackson into Polymorphic Types - A Complete Example is giving me a compile error
This can normally be deserialized by Jackson as follows:
public Object deserialize(String body, Class clazz) {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.readValue(body, clazz);
}
(And this would work if the #JsonUnwrapped annotation were removed)
The Problem
Polymorphic types do not play well with Jackson's #JsonUnwrapped annotation, as discussed in this Jira ticket from 2012:
http://markmail.org/message/pogcetxja6goycws#query:+page:1+mid:pogcetxja6goycws+state:results
Handle polymorphic types with #JsonUnwrapped
Agreed - while fixing things is obviously preferable, improving error messages would be useful if that can't be done.
Unwrapping is one of features where implementations gets complicated enough that any bugs cropping up (on deserialization esp) tend to be antibiotic-resistant...
Hardly encouraging.
Three years later:
http://markmail.org/message/cyeyc2ousjp72lh3
Handle polymorphic types with #JsonUnwrapped
Resolution: Won't Fix
Damn.
So, is there any way to coax Jackson into giving me this behaviour without modifying deserialize() or removing the #JsonUnwrapped annotation?
My SinglePolyUnwrappedDeserializer from this Gist can handle a single polymorphic #JsonUnwrapped property. It's in Kotlin, but can easily be ported to Java if needed. Example:
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type"
)
#JsonSubTypes(
JsonSubTypes.Type(value = A::class, name = "a"),
JsonSubTypes.Type(value = B::class, name = "b")
)
abstract class Base
data class A(val x: Int) : Base()
data class B(val y: Boolean) : Base()
#JsonDeserialize(using = SinglePolyUnwrappedDeserializer::class)
data class C(val a: String, #JsonUnwrapped val b: Base)
AFAIK, all combinations of other annotations are supported. The only limitation is that there is exactly one #JsonUnwrapped property.
If you also need a generic serializer for polymorphic #JsonUnwrapped, you can write it yourself very easily without any reflection or introspection: just merge the ObjectNode of the inner object onto the ObjectNode of the containing object.

Nesting multiple levels of Jackson WRAPPER_OBJECTs

By no means am I a Jackon/JSON wizard, which is probably evident from the following issue I'm running into:
I have 2 possible data structures I'm receiving.
The first one is called amountTransaction:
{
"amountTransaction": {
"clientCorrelator":"54321",
"endUserId":"tel:+16309700001"
}
}
Which is represented by the following Java object:
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT)
#JsonTypeName(value = "amountTransaction")
#JsonInclude(JsonInclude.Include.NON_NULL)
public class AmountTransaction {
private String clientCorrelator;
private String endUserId;
...
}
However the amountTransaction object also appears as child element of the paymentTransactionNotification object:
{
"paymentTransactionNotification": {
"amountTransaction": {
"clientCorrelator": "54321",
"endUserId": "tel:+16309700001"
}
}
}
..which I thought would be represented by:
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT)
#JsonTypeName(value = "paymentTransactionNotification")
#JsonInclude(JsonInclude.Include.NON_NULL)
public class PaymentTransactionNotification {
private AmountTransaction amountTransaction;
...
}
Parsing the JSON with the amountTransaction object alone works fine. It's a pretty straightforward example of a WRAPPER_OBJECT.
However when trying to parse the JSON for the paymentTransactionNotification, I'm getting an exception indicating that it can't properly deal with the amountTransaction as element of the paymentTransactionNotification:
com.fasterxml.jackson.databind.JsonMappingException: Could not resolve type id 'clientCorrelator' into a subtype of [simple type, class com.sf.oneapi.pojos.AmountTransaction]
Any thoughts on how I can properly annotate this so my code can properly deal with both stand alone, as well as encapsulated amountTransaction objects?
By default wrapping root node in Jackson is disabled. You can wrap inner objects but if you want to wrap root node you need to enable jackson feature for it (https://jira.codehaus.org/browse/JACKSON-747):
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.enable(SerializationConfig.Feature.WRAP_ROOT_VALUE);
objectMapper.enable(DeserializationConfig.Feature.UNWRAP_ROOT_VALUE);
When you enabled these features you already said Jackson to wrap the root element and you don't need #JsonTypeInfo and #JsonTypeName anymore. You can simple delete them. But now you need to customize the root node name and you can use #JsonRootName for it. Your classes should look like this:
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonRootName("amountTransaction")
#JsonInclude(JsonInclude.Include.NON_NULL)
public class AmountTransaction {
private String clientCorrelator;
private String endUserId;
...............
}
And
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonRootName("paymentTransactionNotification")
#JsonInclude(JsonInclude.Include.NON_NULL)
public class PaymentTransactionNotification {
private AmountTransaction amountTransaction;
.............
}
I've tried and Jackson converted both JSON requests as expected.

Categories

Resources