I'm building an android app that saves info on incoming/outgoing calls in a CallLog class, incoming/outgoing SMSes in an SmsLog class, and bytes sent/received via a DataLog class. I made them all implement a JsonLog interface so I can create a single ArrayList of JsonLogs, hoping that it will be easily convertible to a JSON array of different objects via Jackson.
However, I kept getting this error whenever I'm deserializing the JSON file:
com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance
of com.project.pojos.JsonLog, problem: abstract types either need to be mapped
to concrete types, have custom deserializer, or be instantiated with additional
type information
How can I solve this? All JsonLog classes have a string attribute type, which is either in_call or out_call for CallLogs, in_sms or out_sms for SmsLogs, and data for DataLog.
You'll need to have a type parameter in your JSON data or a custom deserializer to do the job.
The easier solution is the former, because you won't need to code that much, and it can be easily extended and from the JSON data you could simply tell which entry is what type.
Type information
If you have a type parameter:
[
{
"type": "SMS",
"id": 1,
"data": { }
},
{
"type": "CALL",
"id": 2,
"somethingOtherData": {}
}
]
Then in your abstract JsonLog class you can set the type mapping information:
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
#JsonSubTypes({
#Type(name = "SMS", value = SmsLog.class),
#Type(name = "CALL", value = CallLog.class)
})
public abstract class JsonLog {
}
Deserializer
If you cannot have this type information in your JSON you need to write your own deserializer. In that deserializer you'll need to decide from the actual entry whether this entry is an SmsLog or a CallLog.
Related
I am working with the following structure:
class Family {
//...
Parent parent;
}
class Parent {
//...
}
class ChildA extends Parent {
//...
}
class ChildB extends Parent {
//...
}
I am trying to deserialize a JSON object of type Family (with a Child object and not Parent), but I need to tweak one of the Child so I tried to start by pulling the Parent property from the Family object and then set it equal to a Child. However, the compiler was saying that I needed to typecast it, so I tried but then I got an error basically saying that I couldn't typecast a superclass as a subclass, which makes sense.
I see now that the Family object gets deserialized with Parent only, and no subtypes. How can I deserialize Family with a Child property rather than a Parent type? I don't understand because the Family object gets posted to the server with a Child object, but it gets deserialized only with the Parent properties.
Thanks
After doing some research, I came across what I believe might be part of the solution (although now I have a new problem)
I have revised my classes in the following way:
class Family {
String address;
Parent parent;
}
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = ChildA.class, name = "A"),
#JsonSubTypes.Type(value = ChildB.class, name = "B")
})
class Parent {
String parentAttribute;
}
#JsonSubType("A")
class ChildA extends Parent {
String attributeA;
}
#JsonSubType("B")
class ChildB extends Parent {
String attributeB;
}
Now however, when I try to deserialize the Family class, I get the following error: com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Could not resolve subtype of [simple type, class Family ]: missing type id property 'type' (for POJO property 'Parent')
There is no type field in any of my classes, I think this might have something to do with it.
Here is my JSON string that gets serialized:
{
"Address": "123 Main St",
"Parent": {
"parentAttribute": "Mom",
"attributeA": "Child A Type"
}
}
Ideally, when I perform deserialization, I would do it on the same JSON string above, but the string above doesn't include any type attributes. Is there a way I can sort of pre-process the deserialization and add an intermediary step (i.e. adding a type field?)
Before adding these annotations, I was able to serialize a POJO into a
JSON string like this:
{
"Address": "123 Main St",
"Parent": {
"parentAttribute": "Mom",
"attributeA": "Child A Type"
}
}
Ideally, when I perform deserialization, I would do it on the same
JSON string above, but the dto getting deserialized doesn't use any of
the Child types -- I suppose it doesn't know how to map which Child
object so it just uses Parent, but my knowledge is limited here.
As far as I know, this is not (easily) possible, I'm afraid. Not out-of-the-box and unlikely easily with custom deserialization code.
Try to think from a deserializer point of view: You need something to destinguish JSON objects to map them to a Java class. Currently I only see the presence and absence of some child properties. This would need quite some logic to do the mapping.
If you can change the JSON format, the easiest thing would be to add the property to the Parent which you specified in the following annotation (here: type):
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
You can e.g. pass the value for this property hardcoded to the parent constructor to make sure serialized object has always the correct value.
I was able to resolve this by taking advantage of the fact that one of the properties on the Parent class could be leveraged in the absence of a "type" field.
The problem was that the JSON string being serialized does not include a "type" field, so rather than using:
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
I changed "type" to that property name:
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "theOtherProperty")
This resolved my issues when attempting to deserialize the object. However, it is ultimately a workaround since it is not solving the original issue (doing some form of modification during the deserialization process to add an additional "type" field) but I am unsure of how to go about doing this anyhow.
I'm using Jackson to serialize a heterogeneous list. My list is declared like this:
List<Base> myList = new LinkedList<>();
I have classes Aggregate and Source in there:
myList.add(new Aggregate("count"));
myList.add(new Aggregate("group"));
myList.add(new Source("reader"));
Those classes both implement the Base interface. Each class has just a single property with a get/set method: Aggregate has "type", and Source has "name".
I use this code to try to serialize the list:
ObjectMapper om = new ObjectMapper();
om.configure(SerializationFeature.INDENT_OUTPUT, true);
StringWriter c = new StringWriter();
om.writeValue(c, myList);
System.out.println(c);
but I find the output JSON doesn't have any indication of what type of object was serialized:
[ {
"type" : "count"
}, {
"type" : "group"
}, {
"name" : "reader"
} ]
As such, I don't think I can possibly de-serialize the stream and have it work as I expect. How can I include class information on the serialized representation of each object in a heterogeneous collection such that the collection can be correctly de-serialized?
This is exactly described in http://wiki.fasterxml.com/JacksonPolymorphicDeserialization. Read it, but here are the most relevant parts.
To include type information for the elements, see section 1. There are two alternatives:
om.enableDefaultTyping() will store the class name for elements stored as Object or abstract types (including interfaces). See documentation for overloads. This will work with collections automatically.
Annotate Base with #JsonTypeInfo (e.g. #JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.PROPERTY, property="#class")). To make this work with collections, you'll also need to tell Jackson you want to store a List<Base> (and see section 5):
om.writerWithType(new TypeReference<List<Base>>() {}).writeValue(...);
or
JavaType listBase = objectMapper.constructCollectionType(List.class, Base.class);
om.writerWithType(listBase).writeValue(...);
I've gotten my code working.
At first, I thought I could use WRAP_ROOT_VALUE to get the class information:
om.writer().with(SerializationFeature.WRAP_ROOT_VALUE).writeValue(c, myList);
That didn't work because it only made the root item have class information, and nothing else. I had hoped it would apply recursively.
And so I had to also quit using List<> directly and wrap my List<> object in its own class. Then, the base interface needs a decoration to tell it to write class info:
#JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include= JsonTypeInfo.As.PROPERTY, property="class")
public interface Base {
}
This gets the serializer to write a "class" property with the class value in it.
I am trying to use Jackson to automatically parse my JSON payload to subtypes
All is working as intended and the object is being parsed to the right subtype. but the property used for discriminating is deleted at the end of the process.
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
#JsonSubTypes({
#Type(value = MySubClass.class, name = "type1") })
In this case the property "type" is null in the MySubClass instance.
How do i tell jackson to leave the data intact.
Thanks.
Yes; by default type information is considered to be metadata, and not data to expose to POJOs; similar to how Java type information is distinct from actual properties (albeit accessible via getClass()).
But you can expose type discriminator if you want to by using #JsonTypeInfo(visible=true).
We have the following Json:
{
"type" : "1",
"otherStuff" : "2",
...
"items" : [
{
"commonItemAttribute" : "value",
"specificToType1" : "whatever"
...
}
]
}
We need to polymorphically deserialise the items into different sub classes based on the type attribute.
Is it possible in a custom Jackson deserialiser to get the type value?
Can we safely look back up the Json tree using the JsonParser given to the deserialize method?
I found this blog about polymorphic deserialisation but it seems to require a type attribute on the items themselves.
Thanks
This kind of JSON is not supported, since while "External" type ids of form:
{ "childType" : "SomeTypeId",
"child" : { .... }
}
are supported (with #JsonTypeInfo.As.EXTERNAL_PROPERTY), they only work for simple types, not for Collections or Maps.
So if you can't change JSON to be bit more standard (including type id for elements is the standard way), you will need to use custom serializers, deserializers.
Case is as follows:
I recieve a Json similar to this:
{
nodeProperties:
{
node: {
#id: "00:00:82:b6:ba:b6:5e:43"
#type: "OF"
},
properties: {
property: null,
tables: {
tablesValue: "-2"
},
actions: {
actionsValue: "4095"
},
...
}
}
Deserialization goes through Jackson v.1.9 & Java7
Properties are deserialized and stored into this "nodeProperties" attribute:
private java.util.List<Property> _properties;
And all "properties" fall into subclass polimorphism of Property abstract class. Annotations for this to happen shall they be correctly inserted in the parent class and all subclasses.
Problem is: the JSON includes almost always that "property : null" which should be in fact a pojo class (NullProperty or whatever), or just ignored (as when it is actually null, i don't care at all), but i can't seem to find a way of any of these 2 solutions to make it work.
When I define (for example) a NullProperty subclass (completly void, without anything):
Can not deserialize instance of ...properties.NullProperty out of VALUE_NULL token
Nor i can find a way to ignore the null value. Else when not defining it:
Could not resolve type id 'property' into a subtype of [simple type, class ...properties.Property]
Nor I find the way to ignore it.
Any help will be so greatly appreciated.