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.
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.
ObjectMapper's readValue(InputStream in, Class<T> valueType) function requires the Class. But how do I use it if the class I am passing internally, is having some Interface as data member.
although I can understand the reason behind this exception, as Jackson is not getting the concrete class of the internal Interface of the passed class, but my question is how to resolve it?
how do I deserialize it then? The class I am trying to deserialize is:
class BaseMetricImpl<N> implements Metric<N> {
protected MetricValueDescriptor descriptor;
}
Here MetricValueDescriptor is an interface, so this gives me following error : -
com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of MetricValueDescriptor, problem: abstract types either need to be mapped to concrete types, have custom deserializer, or be instantiated with additional type information
at [Source: java.io.ByteArrayInputStream#2ede2c9f; line: 1, column: 2] (through reference chain: SingleValueMetricImpl["descriptor"])
at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:164)
at com.fasterxml.jackson.databind.DeserializationContext.instantiationException(DeserializationContext.java:624)
at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserialize(AbstractDeserializer.java:115)
at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:375)
at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:98)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:308)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:121)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:2793)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:1989)
Jackson obviously cannot construct the MetricValueDescriptor object since it is an interface. You will need to have additional information in your json and in your ObjectMapper to tell jackson how to construct an object out of it. Here is one way to do it, assuming MVDImpl is a concrete class which implements MetricValueDescriptor:
You can tell Jackson the required type information through a field in the json itself, say "type". To do this, you need to use JsonTypeInfo and JsonSubTypes annotations in your interface. For example,
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
#JsonSubTypes({
#Type(value = MVDImpl.class, name = "mvdimpl") })
interface MetricValueDescriptor
{
...
}
You will need to add a "type":"mvdimpl" field in your json as well.
I was going to point you to the official doc for more info, but then I found an excellent blog covering this topic - Deserialize JSON with Jackson. It covers this topic pretty comprehensively and with examples. So you should definitely read it if you need more customisation.
I see it going one of two ways, but they both require you manually create a concrete class that implements your interface.
Use #Hari Menon's answer and use #JsonSubTypes. This works if you can introduce a type field or something else to trigger which implementation to use.
Use #JsonDeserialize to tell jackson what concrete class it uses by default.
#JsonDeserialize(as = MVDImpl.class)
interface MetricValueDescriptor
{
...
}
Here's a more thorough explanation: https://zenidas.wordpress.com/recipes/jackson-deserialization-of-interfaces/
And the docs: https://fasterxml.github.io/jackson-databind/javadoc/2.8/com/fasterxml/jackson/databind/annotation/JsonDeserialize.html
You don't need to alter the code, you can set it programmatically on the mapper:
static setup() {
final var simpleModule = new SimpleModule()
.addAbstractTypeMapping(<Interface>.class, <Implementation>.class);
objMapper = new ObjectMapper()
.registerModule(new Jdk8Module()) // You probably want this as well
.registerModule(simpleModule);
}
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.
I have a List of javascript objects on my client side, which are a list of "events" that a user has executed. When the user is ready, I want to send this to the server. The order of events is important, so preserving the list order is necessary.
What I would like to do is to have a JSON library (don't mind which one) to bind the JSON to some Event objects in my Java code, where Event is an abstract class, and I have 3 concrete classes that all extend Event (lets say EventA, EventB and EventC).
Ideal scenario would be something like
List<Event> events = jsonlibrary.deserialise(jsonString);
which may contain a list of items such as
[eventA, eventC, eventA, eventA, eventB]
Is this possible, or do I have to inspect the JSON tree manually, and deserialise the individual elements of the json array?
JSON objects are just key/value pairs and contain no type information. That means identifying the type of a JSON object automatically isn't possible. You have to implement some logic on the server-side to find out what kind of event you are dealing with.
I would suggest to use a factory method which takes a json string, parses it to find out what kind of Event it is, builds an Event object of the correct subclass and returns it.
You could use Genson library http://code.google.com/p/genson/.
It can deserialize to concrete types if the json was produced using Genson. Otherwise you only need to add something like [{"#class":"my.java.class", "the rest of the properties"}...]
// an example
abstract class Event {
String id;
}
class Click extends Event {
double x, y;
}
// you can define aliases instead of plain class name with package (its a bit nicer and more secure)
Genson genson = new Genson.Builder().setWithClassMetadata(true).addAlias("click",
Click.class).create();
String json = "[{\"#class\":\"click\", \"id\":\"here\", \"x\":1,\"y\":2}]";
// deserialize to an unknown type with a cast warning
List<Event> events = genson.deserialize(json, List.class);
// or better define to which generic type
GenericType<List<Event>> eventListType = new GenericType<List<Event>>() {};
events = genson.deserialize(json, eventListType);
EDIT
here is the wiki example http://code.google.com/p/genson/wiki/GettingStarted#Interface/Abstract_classes_support
Why not using jackson json library ?
It is a full Object/JSON Mapper with data binding functionnality.
It is fast, small footprint, documented, overused, and many others things you will enjoy!
I began a library that implements the desired fonctionality (for json and xml) if the json is encoded by the same library :
https://github.com/giraudsa/serialisation
to use it,
MyObject myObject = new SpecialisedObject();
String json = JsonMarshaller.ToJson(myObject);
MyObject myClonedObject = JsonUnMarshaller(json);
I have an application which makes use of an external library (Jackson), and the method I need requires a class literal as an argument. So if I wish to parse my JSON string into a User object:
ObjectMapper mapper = new ObjectMapper();
User user = mapper.readValue(new File("user.json"), User.class);
Now, I wish to use this method dynamically (i.e. parse different JSON strings using the same line of code). For example:
String json1 = "{"type":"jacket",...}";
String json2 = "{"type":"sweater",...}";
Object object = mapper.readValue(json1/json2, ???);
//returns a Jacket object OR Sweater object based on the "type" key
//i.e. use Jacket.class as the 2nd argument if "type" is "jacket"
//OR Sweater.class if "type" is "sweater"
//After getting the deserialized object,
//if object is Jacket, cast as a Jacket
//if object is Sweater, cast as a Sweater
Of course, the JSON string in question can be for any class, so I can't simply hard-code an if-else loop. I've looked at custom serializers, but frankly am quite lost at what it's talking about, and would like some help in how I can go about this.
In summary, I need some way to first define a class literal from a String, and then cast the resulting Object into the specific class (but my focus is on getting readValue to work dynamically).
Looks like you need a mapping somewhere between JSON type variable and Java class type.
Generally result should be something like this map:
Map<String, Class<? extends YourSupertype>> map = new HashMap<>();
map.put("sweater", Sweater.class);
map.put("jacket", Jacket.class);
Just store possible clothing types somewhere in a file, then do something like:
String clothingType = nextEntryFromFile();
String className = constructClassNameFromClothingType(clothingType);
map.put(clothingType, Class.forName(className));
Since version 1.5 Jackson supports Polymorphic Type Handling, check here http://www.cowtowncoder.com/blog/archives/2010/03/entry_372.html
there are examples on how to correctly handle deserialization in those cases.