Deserialize json with polymorphic recursion using Jackson - java

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?

Related

Polymorphic model serialization using Jackson

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.

Recommendation for resetting data in json object using Java

I need a recommendation for this situation.
I have a json object in string format that will have pattern like this:
{
"productCard" : {
"productA" : {
"state" : "Y",
"desc" : "AAA",
"someProp" : 112
},
"productB" : {
"state" : "X",
"desc" : " BBB ",
"listSomeThing" : [
{
"p1" : 1,
"p2" : "2"
},
{
"p2" : "3"
}
]
}
// PRODUCT CAN ADD MORE IN FUTRE
// ALSO CAN HAVE OTHER OBJECT TYPE
}
// THIS CAN HAVE OTHER OBJECT THAT MAY BE NON RELATE INFORMATION WITH PRODUCT CARD
}
and then this will be parsed to an object like this:
class Product {
protected String state
protected String desc
}
class SomeThing {
private int p1
private String p2
}
class ProductA extend Product {
private int someProp
}
class ProductB extend Product {
private List<SomeThing> listSomeThing
}
class ProductCard {
private ProductA prodctA
private ProductB productB
}
class BaseObject {
private ProductCard productCard
}
If I need to reset some field value in each product, and then parse to string format again, should I:
(1) create a new function in Product and then override in some child class for extra method:
class Product {
void reset(){
this.state = "X"
this.desc = ""
}
}
class productB extend Product {
#override
void reset(){
super.reset()
this.listSomeThing = new ArrayList<>()
}
}
and in base object create new function:
class ProductCard {
private ProductA productA
private ProductB productB
void resetAllProduct(){
this.productA.reset()
this.productB.reset()
}
}
class BaseObject {
private ProductCard productCard
void resetAllProductCard(){
this.productCard.resetAllProduct()
}
}
then call BaseObject.resetAllProductCard() where business needs to reset?
(2) create new function in business class? Or some util class:
void reset(ProdctCard productCard){
ProductA productA = productCard.getProductA();
productA.setState("X")
productA.setDesc("")
ProductB productB = productCard.getProdctB();
productB.setState("X")
productB.setDesc("")
productB.setListSomeThing(new ArrayList<>())
}
(3) another approach?
I would use Jackson Project for that job:
public String reset(String json) throws IOException {
ObjectMapper mapper = new ObjectMapper();
JsonNode jsonNode = mapper.readTree(json);
JsonNode productCardNode = jsonNode.get("productCard");
productCardNode.forEach(node -> ((ObjectNode) node).put("state", "X").put("desc", ""));
ObjectNode productBNode = (ObjectNode) productCardNode.get("productB");
productBNode.putArray("listSomeThing");
return jsonNode.toPrettyString();
}
Then:
String jsonReseted = reset(json);
System.out.println(jsonReseted);
Output:
{
"productCard" : {
"productA" : {
"state" : "X",
"desc" : "",
"someProp" : 112
},
"productB" : {
"state" : "X",
"desc" : "",
"listSomeThing" : [ ]
}
}
}

Unable to map json response to Object because of multiple implementations

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.

Jackson polymorphic deserialization with type property that is nested in one-level higher object

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.

Order of JSON objects using Jackson's ObjectMapper

I'm using ObjectMapper to do my java-json mapping.
ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
ow.writeValue(new File( fileName +".json"), jsonObj);
this is my java class:
public class Relation {
private String id;
private String source;
private String target;
private String label;
private List<RelAttribute> attributes;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getSource() {
return source;
}
public void setSource(String source) {
this.source = source;
}
public String getTarget() {
return target;
}
public void setTarget(String target) {
this.target = target;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public void setAttributes(List<RelAttribute> attributes) {
this.attributes = attributes;
}
public List<RelAttribute> getAttributes() {
return attributes;
}
}
this is what I get:
{
"id" : "-75da69d3-79c8-4000-a3d8-b10350a57a7e",
"attributes" : [ {
"attrName" : "ID",
"attrValue" : ""
}, {
"attrName" : "Description",
"attrValue" : "Primary Actor"
}, {
"attrName" : "Status",
"attrValue" : ""
} ],
"label" : "new Label",
"target" : "-46b238ac-b8b3-4230-b32c-be9707f8b691",
"source" : "-daa34638-061a-45e0-9f2e-35afd6c271e0"
}
So my question now is, how can I get this json output:
{
"id" : "-75da69d3-79c8-4000-a3d8-b10350a57a7e",
"label" : "new Label",
"target" : "-46b238ac-b8b3-4230-b32c-be9707f8b691",
"source" : "-daa34638-061a-45e0-9f2e-35afd6c271e0",
"attributes" : [ {
"attrName" : "ID",
"attrValue" : ""
}, {
"attrName" : "Description",
"attrValue" : "Primary Actor"
}, {
"attrName" : "Status",
"attrValue" : ""
} ]
}
I want it with same order as in my java declaration. Is there a way to specify it ? Maybe with annotations or stuff like that ?
#JsonPropertyOrder({ "id", "label", "target", "source", "attributes" })
public class Relation { ... }
Do you know there is a convenient way to specify alphabetic ordering?
#JsonPropertyOrder(alphabetic = true)
public class Relation { ... }
If you have specific requirements, here how you configure custom ordering:
#JsonPropertyOrder({ "id", "label", "target", "source", "attributes" })
public class Relation { ... }
The ordering of fields within a generated .class is indeterminate, so you can't count on that.
If you want specific ordering per class then you'll need to use the one of the approaches specified in other answers.
If you want everything to default to alphabetical ordering (e.g. for consistency in how the JSON is structured) then you can configure the ObjectMapper like this:
ObjectMapper mapper = new ObjectMapper();
mapper.setConfig(mapper.getSerializationConfig()
.with(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY));
For more consistent JSON consider also adding:
.with(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS)
One advantage of this approach is that you don't have to modify each class being serialized.
I discovered a third way today in case alphabetic is not your desired sorting order. It turns out adding a #JsonProperty annotation on a field places it last when writing. I discovered that when I wanted to specify a property name which did not conform to java naming conventions.
By Adding an index attribute you can define the order. Lowest index is placed first.
#JsonProperty(index=20)
String prop1;
#JsonProperty(index=10)
String prop2;
Would render:
{"prop2": "valueProp2", "prop1": "valueProp1"}
You can use #XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "response", propOrder = { "prop1", "prop2",
"prop3", "prop4", "prop5", "prop6" }).
#JsonPropertyOrder requires a new jar to be added.
As per this documentation, you can configure Jackson2ObjectMapperBuilder globally. This class is available in spring-web dependency.
#Bean
public Jackson2ObjectMapperBuilder objectMapperBuilder() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.featuresToEnable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY);
return builder;
}
Also, you can use #JsonProperty(index) to determine the order in inherited classes as well.
class animal {
#JsonProperty(index=2)
int p1;
#JsonProperty(index=3)
int p2;
}
class cat extends animal{
#JsonProperty(index=1)
int p3;
}

Categories