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
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.
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
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"
}
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.
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