I have to send request to a third party service where the request JSON looks similar to below. There will be another api which has the same exact JSON format as the response. So I need to build object model that will be able to successfully serialize to/ deserialize from this JSON format.
Sample json
{
"name": "Johnny",
"vehicles": [{
"vehicleType": "car",
"vehicleInfo": {
"maxSeatCount": 2,
"coupe": true
}
}, {
"vehicleType": "truck",
"vehicleInfo": {
"towingCapacity": "3000lb",
"bedLength": "6ft"
}
}]
}
Here are the POJOs I created to meet the above model.
PersonInfo.java
public class PersonInfo {
private String name;
private List<Vehicle> vehicles;
}
Vehicle.java
public class Vehicle {
private String vehicleType;
private VehicleInfo vehicleInfo;
}
VehicleInfo.java
#JsonTypeInfo(use = Id.NAME,
include = JsonTypeInfo.As.EXTERNAL_PROPERTY,
property = "vehicleType")
#JsonSubTypes({
#Type(value = CarInfo.class, name="car"),
#Type(value = TruckInfo.class, name="truck")
})
public abstract class VehicleInfo {
}
CarInfo.java
public class CarInfo extends VehicleInfo{
private int maxSeatCount;
private boolean coupe;
}
TruckInfo.java
public class TruckInfo extends VehicleInfo{
private String towingCapacity;
private String bedLength;
}
I'm running into two problems with this model. During serialization, the JSON generated has the attribute vehicleType inside vehicleInfo object as well. It should not be.
JSON generated using above model.
{
"name" : "Johnny",
"vehicles" : [ {
"vehicleType" : "car",
"vehicleInfo" : {
"vehicleType" : "car", // this shouldn't be here
"maxSeatCount" : 2,
"coupe" : false
}
}, {
"vehicleType" : "truck",
"vehicleInfo" : {
"vehicleType" : "truck", // this shouldn't be here
"towingCapacity" : "3000lb",
"bedLength" : "6ft"
}
} ]
}
Second issue is that during deserialization, Jackson is complaining that it doesn't see the vehicleType attribute in vehicleInfo type.
Exception in thread "main"
com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Could not
resolve subtype of [simple type, class
com.kodakandla.file.VehicleInfo]: missing type id property
'vehicleType' (for POJO property 'vehicleInfo') at [Source:
(String)"{"name":"Johnny","vehicles":[{"vehicleType":"car","vehicleInfo":{"maxSeatCount":2,"coupe":true}},{"vehicleType":"truck","vehicleInfo":{"towingCapacity":"3000lb","bedLength":"6ft"}}]}";
line: 1, column: 95] (through reference chain:
com.kodakandla.file.PersonInfo["vehicles"]->java.util.ArrayList[0]->com.kodakandla.file.Vehicle["vehicleInfo"])
at
com.fasterxml.jackson.databind.exc.InvalidTypeIdException.from(InvalidTypeIdException.java:43)
at
com.fasterxml.jackson.databind.DeserializationContext.missingTypeIdException(DeserializationContext.java:2083)
at
com.fasterxml.jackson.databind.DeserializationContext.handleMissingTypeId(DeserializationContext.java:1596)
What changes do I need to make for this to work?
I figured out what was wrong with my POJOs. Thought I would post the answer just in case if anyone else runs into similar issue.
I have the #JsonTypeInfo annotation in the wrong place. Instead of setting it at the class level in the VehicleInfo class, I had to set it at the field level in the Vehicle class.
public class Vehicle {
private String vehicleType;
#JsonTypeInfo(use = Id.NAME,
include = JsonTypeInfo.As.EXTERNAL_PROPERTY,
property = "vehicleType")
#JsonSubTypes({
#Type(value = CarInfo.class, name="car"),
#Type(value = TruckInfo.class, name="truck")
})
private VehicleInfo vehicleInfo;
}
Now the serialization and deserialization are working as expected.
Related
I am trying to deserialize an object which has polymorphic recursion on a Map value. The first level always deserialize to the correct type, but the recursions are deserialized as LinkedHashMap.
Code:
public class Rule {
private String ruleName;
private List<Variable> variables;
}
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
#JsonSubTypes({
#Type(value = ListVariable.class, name = "ListVariable"),
#Type(value = MapVariable.class, name = "MapVariable")
})
public abstract class Variable<T> {
private String name;
private T value;
}
public class ListVariable extends Variable<List<Object>> {
}
public class MapVariable extends Variable<Map<String, Object>> {
private String input;
private Object defaultValue;
}
Json with multiple levels:
{
"ruleName" : "xyz",
"variables" : [ {
"type" : "MapVariable",
"name" : "multi_level_map",
"input" : "listing_country",
"defaultValue" : "Unknown",
"value" : {
"US" : {
"type" : "MapVariable",
"name" : "multi_level_map_2_us",
"input" : "threshold",
"defaultValue" : "Unknown",
"value" : {
"Range(0.0,1.0)" : "Low_US",
"Range(1.0,2.0)" : "Medium_US",
"Range(2.0,3.0)" : "High_US"
}
}
}
} ]
}
Deserialization results:
enter image description here
Does anyone know what I am doing wrong and how can I make this recursive deserialization work?
I have this class structure:
ResponseBody.java:
public class ResponseBody {
private String type;
private A a;
public static class A {
private List<B> b;
}
}
B.java:
public class B {
private String c;
private DynamicJson dynamicJson;
}
DynamicJson.java:
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME,
include = As.PROPERTY, property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = Child1.class, name = "child-1"),
#JsonSubTypes.Type(value = Child2.class, name = "child-2"),
#JsonSubTypes.Type(value = Child3.class, name = "child-3")
})
public abstract class DynamicJson {
private String onlyKnownField;
}
Child1.java
#JsonTypeName("child-1")
public class Child1 extends DynamicJson {
// Number of fields and field names here change based on which implementation of Dynamic Json
}
Likewise, Child2 and Child3 classes.
Removed constructors, setters, getters.
I want to cast/deserialize below json to ResponseBody.java class.
{
"type": "child-1",
"a": {
"b": [
{
"c": "guid",
"dynamicJson": {
"onlyKnownField": "guid", // only field that always comes
/*
dynamic number of fields based on "type", say 2 fields, if type is
something else then number of fields here is different
*/
}
}
]
}
}
The field "dynamicJson" can have different number of fields with different names inside it based on "type" value. Which seems to be the problem,
This is the code being used:
ObjectMapper objectMapper = new ObjectMapper();
ResponseBody responseBody = objectMapper.readValue(json.getBody(), ResponseBody.class);
However, above code give this error:
Missing type id when trying to resolve subtype of [simple type, class DynamicJson]
What am I doing wrong here?
Let me know if other info is needed.
I am stuck deserializing a JSON using Jackson.
JSON objects:
{
"animal": {
"header": {
"id": "1",
"type": "dog"
}
"description": {
"color":"black",
"sound": "bark"
"loyal": true
}
}
{
"animal": {
"header": {
"id": "2",
"type": "cat"
}
"description": {
"color": "white",
"sound": "meow",
"sleepDuration": 14
}
}
{
"plant": {
"header": {
"id": "5",
"type": "rose"
}
"description": {
"color": "red",
"smell": "good"
}
}
Java Classes:
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.WRAPPER_OBJECT
)
#JsonSubTypes({
#JsonSubTypes.Type( value = Animal.class, name = "animal" ),
#JsonSubTypes.Type( value = Plant.class, name = "plant")
})
#JsonRootName("species")
public abstract class Species {
public Header header;
public Description description;
//...
}
#JsonRootName("animal")
public class Animal extends Species {
//...
}
#JsonRootName("plant")
#Getter
public class Plant extends Species {
//...
}
#JsonTypeInfo(
// ????????? Not sure if there is an annotation to help with this.
)
#JsonSubTypes({
#JsonSubTypes.Type(name = "DogDescription", value = DogDescription.class),
#JsonSubTypes.Type(name = "RoseDescription", value = RoseDescription.class),
#JsonSubTypes.Type(name = "CatDescription", value = CatDescription.class)})
public abstract class Description {
private String color;
}
#JsonRootName("description")
public class DogDescription {
private Sound sound;
private boolean loyal;
}
#JsonRootName("description")
public class CatDescription {
private Sound sound;
private int sleepDuration;
}
The serialization of animal succeeded. However, I have issue on how to serialize the description using type information in the header.
use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, doesn't work since the type property is not under the Description Object. I have tried also to do a customTypeId resolver as described in this post : Jackson polymorphic deserialization with type property that is nested in object. It didn't work. I think because once the species serialization is performed, Jackson would pass the JSON tree of Description. I couldn't find a way to access the type in the header.
I am wondering if there is a solution to this problem without using a custom serializer.
I used the JsonTypeInfo to correctly deserialize the json to appropriate sub-types
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
public abstract class Car {
}
public class Audi extends Car {
// Getters and Setters
}
public class Merc extends Car {
// Getters and Setters
}
For the following structure - Map<String, Car> info
Here is my sample JSON
{
info: {
"xyz": {
type: "Audi"
},
"abc": {
type: "Merc"
}
}
}
The above JSON does not deserialize correctly and get the error 400: Unable to process JSON. What am I missing here ?
Two things, the JSON is incorrect because the properties needs to be between quotes:
{
"info": {
"xyz": {
"type": "Audi"
},
"abc": {
"type": "Merc"
}
}
}
And the second, try to add this annotation to the abstract class:
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = Audi.class, name = "Audi"),
#JsonSubTypes.Type(value = Merc.class, name = "Merc")})
abstract class Car {
It basically indicates the subtypes and the names associated.
Here is a json snippet which contains an array(icons) which can contain two different types of objects(application and folder)
{
"icons": [
{
"application": {
"displayName": "Facebook",
"bundleId": "com.facebook.com"
}
},
{
"folder": {
"some": "value",
"num": 3
}
}
]
}
How can I create java POJO's modelling this kind of json and then deserialize the same?
I referred to this question. But I can't change the json I'm getting to include a 'type' as advised there and then use inheritance for the POJO's of the two different objects.
No custom deserializers are required. A smart #JsonTypeInfo will do the trick.
See below what the classes and interfaces can be like:
#JsonTypeInfo(use = Id.NAME, include = As.WRAPPER_OBJECT)
#JsonSubTypes({ #Type(value = ApplicationIcon.class, name = "application"),
#Type(value = FolderIcon.class, name = "folder") })
public interface Icon {
}
#JsonRootName("application")
public class ApplicationIcon implements Icon {
public String displayName;
public String bundleId;
// Getters and setters ommited
}
#JsonRootName("folder")
public class FolderIcon implements Icon {
public String some;
public Integer num;
// Getters and setters ommited
}
public class IconWrapper {
private List<Icon> icons;
// Getters and setters ommited
}
To deserialize your JSON, do as following:
String json = "{\"icons\":[{\"application\":{\"displayName\":\"Facebook\",\"bundleId\":\"com.facebook.com\"}},{\"folder\":{\"some\":\"value\",\"num\":3}}]}";
ObjectMapper mapper = new ObjectMapper();
IconWrapper iconWrapper = mapper.readValue(json, IconWrapper.class);