Deserializing a List of Strings with defaultTyping - java

I'm facing a weird issue during deserializing of a JSON file that has two lists declared in different ways.
My json file looks like this
{
"name": "abc",
"version": 1,
"fileNames": [
"xyz.abc",
"xyz/abc"
]
"vehicles": [
"java.util.ArrayList",
[
{
"name": "Car"
},
{
"name": "Train"
},
]
]
}
My code looks like:
private static final ObjectMapper OBJECT_MAPPER;
static {
OBJECT_MAPPER = new ObjectMapper();
OBJECT_MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
OBJECT_MAPPER.configure(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL, true);
OBJECT_MAPPER.addMixIn(Vehicle.class, VehicleMixIn.class);
OBJECT_MAPPER.enableDefaultTyping();
}
#JsonCreator
public Config(
#JsonProperty(value = "name", required = true) final String name,
#JsonProperty(value = "version") final Integer version,
#JsonProperty(value = "Vehicles") List<Vehicle> listofVehicles,
#JsonProperty(value = "paths") final List<String> paths, {
fileName = name;
versionNumber = version;
vehicles = listofVehicles;
mPaths = paths;
}
/**
* A JSONMixin helper class to deserialize Vehicles.
*/
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "name")
#JsonSubTypes({
#JsonSubTypes.Type(name = "Car", value = Car.class),
#JsonSubTypes.Type(name = "Train", value = Train.class),
#JsonSubTypes.Type(name = "Bus", value = Bus.class)
})
private abstract class VehicleMixIn {
public VehicleMixIn() {
}
}
The way I read the json file
try (BufferedReader reader = Files.newBufferedReader(configFile.toPath(), StandardCharsets.UTF_8)) {
config = Config.fromJson(reader);
}
I'm unable to read the polymorphic vehicle type.
I get the following error:
com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Could not resolve type id 'xyz.abc' into a subtype of [collection type; class java.util.List, contains [simple type, class java.lang.String]
This works if I remove OBJECT_MAPPER.enableDefaultTyping(), however then I'm unable to read the 'Vehicles' field. How do I make this work?
I've tried all types of defaultTyping and also tried to use a custom deserializer.

Related

How to identify different types automatically using Jackson mapper

I have a simple situation. I have a main DTO class with the following fields:
AnimalDTO
public class AnimalDTO {
#JsonCreator
public AnimalDTODTO(#JsonProperty("error") boolean error,
#JsonProperty("errorMessage") String errorMessage,
#JsonProperty("cat") CatDTO cat,
#JsonProperty("dog") DogDTO dog) {
this.error = error;
this.errorMessage = errorMessage;
this.cat = cat;
this.dog = dog;
}
private boolean error;
private String errorMessage;
private CatDTO cat;
private DogDTO dog;
}
CatDTO:
public class CatDTO {
#JsonCreator
public CatDTO(#JsonProperty("catNumber") String catNumber,
#JsonProperty("catState") String catState) {
this.catNumber = catNumber;
this.catState = catState;
}
private String catNumber;
private String catState;
}
DogDTO:
public class DogDTO {
#JsonCreator
public DogDTO(#JsonProperty("dogNumber") String dogNumber,
#JsonProperty("dogState") String dogState
#JsonProperty("dogColor") String dogColor
#JsonProperty("dogBalance") BigDecimal dogBalance) {
this.dogNumber = dogNumber;
this.dogState = dogState;
this.dogColor = dogColor;
this.dogBalance = dogBalance;
}
private String dogNumber;
private String dogState;
private String dogColor;
private BigDecimal dogBalance;
}
and from external API I have responses (and I can't change it) for dog like:
{
"dogNumber": "23",
"dogState": "good",
"dogColor": "pink",
"dogBalance": 5
}
and for Cat:
{
"catNumber": "1",
"catState": "good"
}
And I want to use Jackson mapper like this: objectMapper.readValue(stringJson, AnimalDTO.class);
I was thinking to add in AnimalDTO:
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
#JsonSubTypes({
#Type(value = CatDTO.class, name = "cat"),
#Type(value = DogDTO.class, name = "dog")
})
but it's not working.
How to handle my case in best way?
Your code will work just fine (without any #JsonTypeInfo or #JsonSubTypes annotations) if the JSON that you get from the external API is as follows:
{
"dog": {
"dogNumber": "23",
"dogState": "good",
"dogColor": "pink",
"dogBalance": 5
},
"cat": {
"catNumber": "1",
"catState": "good"
}
}
If this does not look similar to the JSON you receive then you need to add it to your answer so that we can help you further.

Jackson polymorphism issue without enableDefaultTyping

I'm facing a weird issue during deserializing of a JSON file that has polymorphic types.
My json file looks like this
{
"name": "abc",
"version": 1,
"fileNames": [
"xyz.abc",
"xyz/abc"
]
"vehicles": [
"java.util.ArrayList",
[
{
"name": "Car"
},
{
"name": "Train"
},
]
]
}
My code looks like:
private static final ObjectMapper OBJECT_MAPPER;
static {
OBJECT_MAPPER = new ObjectMapper();
OBJECT_MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
OBJECT_MAPPER.configure(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL, true);
OBJECT_MAPPER.addMixIn(Vehicle.class, VehicleMixIn.class);
}
#JsonCreator
public Config(
#JsonProperty(value = "name", required = true) final String name,
#JsonProperty(value = "version") final Integer version,
#JsonProperty(value = "Vehicles") List<Vehicle> listofVehicles,
#JsonProperty(value = "paths") final List<String> paths, {
fileName = name;
versionNumber = version;
vehicles = listofVehicles;
mPaths = paths;
}
/**
* A JSONMixin helper class to deserialize Vehicles.
*/
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "name")
#JsonSubTypes({
#JsonSubTypes.Type(name = "Car", value = Car.class),
#JsonSubTypes.Type(name = "Train", value = Train.class),
#JsonSubTypes.Type(name = "Bus", value = Bus.class)
})
private abstract class VehicleMixIn {
public VehicleMixIn() {
}
}
The way I read the json file
try (BufferedReader reader = Files.newBufferedReader(configFile.toPath(), StandardCharsets.UTF_8)) {
config = Config.fromJson(reader);
}
I'm unable to read the polymorphic vehicle type.
I get the following error:
com.fasterxml.jackson.databind.JsonMappingException: Unexpected token (VALUE_STRING), expected FIELD_NAME: missing property 'name' that is to contain type id (for class abc.def.Vehicle)
This works if I do OBJECT_MAPPER.enableDefaultTyping(), however then I'm unable to read the 'paths' field. How do I make this mixIn work?
I've tried all types of defaultTyping

How to ignore case for POJO if it extends an abstract class in Spring Jackson?

I know we can ignore case for input JSON by adding property in application.yml as:
spring:
jackson:
mapper:
accept_case_insensitive_properties: true
But if my POJO extends an abstract class, it is not working and my JSON is not being parsed.
My abstract class:
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "event")
#JsonSubTypes({
#JsonSubTypes.Type(value = Orders.class, name = "orders"),
#JsonSubTypes.Type(value = WorkOrders.class, name = "workOrders")
})
public abstract class ElasticDocument {
// Fields and getter/setter
}
My Pojo:
#JsonIgnoreProperties(ignoreUnknown = true)
#Data
public class Orders extends ElasticDocument {
//other fields
private List<OrderLine> orderLines;
}
Input JSON which I am getting from input has different case e.g.
{
"event": "orders",
"OrderNo": 12345,
"Status": "Created",
"CustomerZipCode": "23456",
"CustomerFirstName": "firstname1",
"orderType": "PHONEORDER",
"customerLastName": "lastname1",
"OrderLines": [
{
"LineName": "sample"
}
]
}
My contoller method where I am using this ElasticDocument object:
#PostMapping("save")
public Orders save(#RequestBody ElasticDocument elasticDocument) {
return elasticsearchRepository.save((Orders) elasticDocument);
}
I am using Spring-boot version 2.2.4
I think you forgot to add #type to your request JSON.#type is to identify the type of ElasticDocument being serialized.
Here is a example that i tried in my local system with minimum fields in class:
ElasticDocument.java
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY)
#JsonSubTypes({
#JsonSubTypes.Type(value = Orders.class, name = "Orders"),
#JsonSubTypes.Type(value = WorkOrders.class, name = "workOrders")
})
public abstract class ElasticDocument {
private Integer docId;
private String docName;
// getters and setters
}
Orders.java
public class Orders extends ElasticDocument{
private Integer orderId;
private String orderName;
// getters and setters
}
WorkOrders.java
public class WorkOrders extends ElasticDocument{
private Integer workOrderId;
private String workOrderName;
// getters and setters
}
StackOverflowController.java
#RestController
#RequestMapping("/api/v1")
public class StackOverflowController {
#PostMapping("/orders")
ElasticDocument createOrder(#RequestBody ElasticDocument order){
return order;
}
}
When i send data like this to my endpoint (Please note the attributes name in json are lowercase)
{
"#type":"workOrders",
"docId":123,
"docName":"XXXX",
"orderid":45,
"ordername":"shoe",
"workorderid":324,
"workordername":"dsf"
}
It is converted to workOrders response:
{
"#type": "workOrders",
"docId": 123,
"docName": "XXXX",
"workOrderId": 324,
"workOrderName": "dsf"
}
And when i changed the #type to Orders in request then i will get Order response:
{
"#type": "Orders",
"docId": 123,
"docName": "XXXX",
"orderId": 45,
"orderName": "shoe"
}

spring restTemplate dynamic mapping

I am using spring restTemplate to map a response to a POJO.
The response of the rest api is like this:
"attributes": {
"name": {
"type": "String",
"value": ["John Doe"]
},
"permanentResidence": {
"type": "Boolean",
"value": [true]
},
"assignments": {
"type": "Grid",
"value": [{
"id": "AIS002",
"startDate": "12012016",
"endDate": "23112016"
},{
"id": "AIS097",
"startDate": "12042017",
"endDate": "23092017"
}]
}
}
in the parent class, I have:
public class Users {
private Map<String, Attribute> attributes;
}
If all the values of were String type, then I could have done like:
public class Attribute {
private String type;
private String[] value;
}
But the values are of different types. So I thought of doing the following:
public class Attribute {
private String type;
private Object[] value;
}
The above should work, but at every step I have to find out what is the type of Object.
So, my question is can I have something like this:
public class Attribute {
#JsonProperty("type")
private String type;
#JsonProperty("value")
private String[] stringValues;
#JsonProperty("value")
private Boolean[] booleanValues;
#JsonProperty("value")
private Assignments[] assignmentValues; // for Grid type
}
But it is not working and throwing errors: Conflicting setter definitions for property "value"
What is the recommended way of handling this scenario?
I would recommend Jackson facilities for handling polymorphism here:
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = BooleanAttribute.class, name = "Boolean"),
#JsonSubTypes.Type(value = StringAttribute.class, name = "String")
})
class Attribute {
private String type;
}
class BooleanAttribute extends Attribute {
private Boolean[] value;
}
class StringAttribute extends Attribute {
private String[] value;
}
JsonTypeInfo tells Jackson that this is a base class and the type will be determined by a JSON field named "type"
JsonSubTypes maps subtypes of Attribute to values of "type" in JSON.
If you add an appropriate subtype for Assignments and getters/setters Jackson will be able to parse your JSON.

Polymorphic deserialization jackson

I am trying to implement polymorphic deserialization in jackson and trying to make the same model work in two places.
I have ShopData object
public class ShopData extends Embeddable implements Serializable
{
private final int id;
private final String name;
private final String logoImageUrl;
private final String heroImageUrl;
public ShopData(#JsonProperty(value = "id", required = true) int id,
#JsonProperty(value = "name", required = true) String name,
#JsonProperty(value = "logoImageUrl", required = true) String logoImageUrl,
#JsonProperty(value = "heroImageUrl", required = true) String heroImageUrl)
{
this.id = id;
this.name = name;
this.logoImageUrl = logoImageUrl;
this.heroImageUrl = heroImageUrl;
}
}
My Embeddable object looks like this
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.WRAPPER_OBJECT)
#JsonSubTypes({#JsonSubTypes.Type(value = AnotherObject.class, name = "AnotherObject"),
#JsonSubTypes.Type(value = ShopData.class, name = "shop")
})
public abstract class Embeddable
{
}
I am trying to make this model work in two places. This model works as expected.
public Order(#JsonProperty(value = "_embedded", required = true) Embeddable embedded)
{
this.embedded = (ShopData) embedded;
}
"_embedded": {
"shop": {
"id": 1,
"name": "",
"freshItems": 5,
"logoImageUrl": "",
"heroImageUrl": "",
"_links": {
"self": {
"href": "/shops/1"
}
}
}
While this doesn't
public ShopList(#JsonProperty(value = "entries", required = true) List<ShopData> entries)
{
this.entries = Collections.unmodifiableList(entries);
}
{
"entries": [
{
"id": 1,
"name": "",
"freshItems": 5,
"logoImageUrl": "",
"heroImageUrl": "",
"_links": {
"self": {
"href": "/shops/1"
}
}
}
]
}
And throws error :Could not resolve type id 'id' into a subtype
I understand the error but do not know how to resolve this. I would like to be able to use the same model in both cases. Is this possible?
Just found out the answer myself. Should have simply added this anotation
#JsonTypeInfo(use= JsonTypeInfo.Id.NONE)
public class ShopData extends Embeddable implements Serializable

Categories

Resources