How to handle parent child relation for Rest service - java

In my Spring boot service, I have a controller as below
#PostMapping
public ApiResponse generateUKLabel(#RequestBody LabelRequestData data){
//do operation
}
Here, LabelRequestData has List of base class.
In, request I am passing child class data with base class data.
But, Child class data seems to be ignored.
Is there any way I can access the child class data.
I want to make the LabelRequestData with Base class generic so that it can accept multiple child class data.
Is there any way to solve it?
I tried casting. but throws can't cast exception
I also tried generics. but then I need to create two controller method to handle those request. I want only one controller method to handle the request
#Data
public class LabelRequestData extends BaseRequestData {
#Valid
private List<BaseClass> labelData; // this base class has multiple child classes that i need
#Valid
private PreferenceData preferenceData;
}

When Deserialising the Jackson will not know which child class that has to be used, so it only takes the value of the BaseClass and ignores the ChildClass
You could use #JsonTypeInfo in the BaseClass , this helps the Jackson to identify the proper ChildClass (You have to add type in the json)
I am not sure what BaseClass holds so I am just assuming random attributes below.
BaseClass.java
#Data
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = ChildOne.class, name = "childOne"),
#JsonSubTypes.Type(value = ChildTwo.class, name = "childTwo")})
public class BaseClass {
private Integer id;
}
ChildOne .java
#Data
public class ChildOne extends BaseClass {
private String name;
}
ChildTwo.java
#Data
public class ChildTwo extends BaseClass {
private String address;
}
If you request the Json,
{
"labelData": [
{
"id": 0, // -|
"type": "childOne", // |-> This will go to ChildOne.class
"name": "Foo" // _|
}, {
"id": 0, // -|
"type": "childTwo", // |-> This will go to ChildTwo.class
"address": "Somewhere in Earth"// _|
}
]
// Rest of Json
}

Related

Spring Java POST endpoint that can have a field be different data types

I'm working on a Java Spring Boot HTTP Service application. I currently have a POST endpoint that I have defined inside of a #RestController. This controller, called processRequest takes an object called Info with the #RequestBody annotation.
Right now, I have it setup where a user can send JSON based on the Info class that I defined like this:
//Sample JSON Payload
{
"name": "Bob",
"age": 26,
"hobby": biking
}
//Sample Object
#RequiredArgsConstructor
public class Info {
public final String name;
public final int age;
public final String hobby
}
What I want to do know is respond to the situation where one of the fields is sent as a different datatype. For example:
//JSON payload with different datatype for a field
{
"name": "Bob",
age: 26,
"hobby": ["biking", "hiking"] //This is supposed to be a string but it's an array.
}
Is it possible to keep the endpoint properties the same but handle different data types? Maybe I can create another class where the fields are different and spring will automatically create the one that matches the input? I'm curious for what the best approach to this problem would be.
In this particular example, where the hobby could either be a single value or multiple values, I would rely on the Jackson ACCEPT_SINGLE_VALUE_AS_ARRAY deserialization feature.
This can be configured application-wide within application.properties:
spring.jackson.deserialization.accept-single-value-as-array=true
Or this can be enabled for a specific field:
#RequiredArgsConstructor
public class Info {
public final String name;
public final int age;
#JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
public final List<String> hobby
}
For more complex cases, Jackson recommends that you write a wrapper with a specific type field to provide a hint of which type it should deserialize. For example:
public class InfoWrapper {
private String type;
#JsonTypeInfo(use = Id.NAME, property = "type", include = As.EXTERNAL_PROPERTY)
#JsonSubTypes(value = {
#JsonSubTypes.Type(value = PersonInfo.class, name = "person")
})
private Info info;
}
public interface Info {}
#RequiredArgsConstructor
public class PersonInfo implements Info {
public final String name;
public final int age;
public final String hobby
}
So, if you want to send a JSON containing PersonInfo, you can use the following JSON:
{
"type": "person",
"info": {
"name": "Bob",
"age": 26,
"hobby": "biking"
}
}
If you need more advanced control over what you want to do, you can write a custom deserializer and apply it with the #JsonDeserialize annotation.
You can use JsonNode for the field which changes. As below:
public class Info {
public String name;
public int age;
public JsonNode hobby;
#Schema(description = "")
#Valid
#JsonIgnore
public List<String> getHobbies() {
// if hobby is array create Java object and return
// else if hobby is String create List, add the string to it and return it.
// or some other solution of your choice and requirement
}
}

Missing type id when trying to resolve subtype of simple type, missing type id property type

I am trying to write a JUNIT for my serialization class .Version i am using for jackson is jackson-databind-2.10.2 My Concrete class extends and abstract class . When i am running my JUNIT i am getting an exception, Missing type id when trying to resolve subtype of [simple type,ReshipInfo]: missing type id property 'type'
Below is my JSON to serialize . What i am missing. Please help.
{
"orderId" : "12345",
"orderDocumentType" : "SALES"
}
JUNIT Call
InputStream is = this.getClass().getClassLoader().getResourceAsStream("samples/response.json");
String control = IOUtils.toString(is, Charsets.UTF_8);
ReshipInfo reshipInfo = objectMapper.readValue(control, ReshipInfo.class);
Concrete Class
public class ReshipInfo extends AbstractRequest {
private Integer returnGracePeriod;
public ReshipInfo() {
}
public ReshipInfo(Builder builder) {
super(builder.orderDocumentType);
returnGracePeriod = builder.returnGracePeriod;
}
}
Abstract Class
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXISTING_PROPERTY,
property = "type",
visible = true)
#JsonSubTypes({
#JsonSubTypes.Type(value = ReshipInfo.class, name = "SALES")
})
public abstract class AbstractRequest {
#JsonProperty(value = "type")
private OrderDocumentType orderDocumentType;
}
I am using an abstract class (in your case - AbstractRequest) while config SetupContext of Jackson.
Like:
context.setMixInAnnotations(ReshipInfo.class, AbstractRequest.class)
So my entity extends ReshipInfo as abstract, and ReshipInfo does not extend AbstractRequest.

Writing custom deserializer for polymorphic type hierarchy Jackson

I'm experimenting with Jackson deserialization for inheritance in Java.
I've a base class:
#Getter //Lombok #Getter
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", include = JsonTypeInfo.As.PROPERTY)
#JsonSubTypes(value = {
#JsonSubTypes.Type(value=ClassA.class, name = "classA")
})
public abstract class BaseClass {
private List<String> fields;
#JsonCreator
public BaseClass(#JsonProperty("fields") final List<String> fields) {
this.fields = fields;
}
}
ClassA is also abstract
#Getter
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "typeA", include = JsonTypeInfo.As.PROPERTY)
#JsonSubTypes(value = {
#JsonSubTypes.Type(value=SubClassA.class, name = "subclassA")
})
public abstract class ClassA extends BaseClass{
private String mode;
#JsonCreator
public ClassA(#JsonProperty("fields") final List<String> fields, #JsonProperty("mode") String mode) {
super(fields);
this.mode = mode;
}
}
My subClassA:
public class SubClassA extends ClassA {
private String dummyField;
public SubClassA(#JsonProperty("fields") final List<String> fields, #JsonProperty("mode") String mode,
#JsonProperty("dummyField") String dummyField) {
super(fields, mode);
this.dummyField = dummyField;
}
}
If I pass in a JSON of in the following form:
{
"type": "classA",
"typeA": "subclassA",
"mode": "testingMode",
"fields": ["1", "2"],
"dummyField": "dummy"
}
I get an error Cannot construct instance of ClassA (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
I came across this https://github.com/FasterXML/jackson-databind/issues/374 which says this is a known issue with Jackson.
How do I go about writing a customDeserializer for this.
In classA I tried doing this:
#JsonDeserialize(using = ClassADeserializer.class)
and ClassADeserializer is:
public class ClassADeserializer extends StdDeserializer<ClassA> {
private final JsonDeserializer<?> defaultDeserializer;
public ClassADeserializer(JsonDeserializer<?> defaultDeserializer) {
super(ClassA.class);
this.defaultDeserializer = defaultDeserializer;
}
#Override public ClassA deserialize(final JsonParser jsonParser, final DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
return (ClassA) defaultDeserializer.deserialize(jsonParser, deserializationContext);
}
which obviously doesn't work. How do I go about writing a custom deserializer for this?
Problem:
You pass in json "type": "classA",... That means jackson first try to create instance of ClassA..During deserialization jackson search #JsonCreator constructor first..If #JsonCreator missing or can not call #JsonCreator constructor then jackson create object with default constructor and call setter method... In your ClassA #JsonCreator constructor with 2 arguments but jackson call with 3 arguments.. So its fail. then jackson call default constructor to create instance. but default constructor also missing.. thats why u get this error: Cannot construct instance of ClassA (no Creators, like default construct, exist)..
Solution:
As you want to deserialize to SubClassA... You need to use #JsonCreator in SubClassA...Then you need to use #JsonIgnoreProperties to ignore properties type so that jackson create instance of SubClassA instead of ClassA....
Try with below SubClassA:
#Getter
#JsonIgnoreProperties(ignoreUnknown = true)
public class SubClassA extends ClassA {
private String dummyField;
#JsonCreator
public SubClassA(#JsonProperty("fields") final List<String> fields, #JsonProperty("mode") String mode,
#JsonProperty("dummyField") String dummyField) {
super(fields, mode);
this.dummyField = dummyField;
}
}

Ignore JsonDeserializer when deserialising sub classes objects

I have parent class on which I have specified a custom deserialiser like this -
#JsonDeserialize(using = CustomDeserializer.class)
public class ParentClass {
}
I have subclasses extending above class and I don't want those classes to use CustomDeserializer for deserialisation purpose. I know using Will ignore the CustomDeserializer class during serialisation.
#JsonDeserialize(as = Child.class)
public class ChildClass extends ParentClass {
}
The question is - Is there any other way to tell ObjectMapper or anything else which will ignore this CustomDeserializer without specifically specifying #JsonDeserialize(as = Child.class) on every child class ?
Remove the annotation from the ParentClass and create a new subclass with the annotation. Use this new subclass when you want to deserialize to a ParentClass. As the custom deserialize annotation is on the subclass you can return an instance of ParentClass.
public class ParentClass {
// fields in the ParentClass
}
#JsonDeserialize(using = CustomDeserializer.class)
public class ParentWithCustomDeserialize extends Parent {
}
Then you can simply...
Parent parent = objectMapper.readValue(jsonString, ParentWithCustomDeserialize.class);

How to get a specific attribute an object in JSON?

I working with JAX-RS and I want to get a JSON Object from my resource.
For example, I have the next code:
Book class:
#XmlRootElement
public class Book {
private int id;
private String name;
}
And person class:
#XmlRootElement
private class Person {
private int id;
#XmlElement(name="full_name")
private String fullName;
#XmlElement(name="book_id")
private Book book;
}
I want get this:
{
"id": 1,
"full_name": "Gustavo Pacheco",
"book_id": 8
}
And don't get this:
{
"id": 1,
"full_name": "Gustavo Pacheco",
"book": {
"id": 8,
"name": "Cien AƱos De Soledad"
}
}
How can I get only id attribute from book class for get a more simple JSON?
The best thing for these cases is to have the classes corresponding to the DTOs and entities corresponding to the database model independently.
For example:
package com.github.ryctabo.example.entity;
/* Entity class */
#Entity
public class Person implements Serializable {}
package com.github.ryctabo.example.dto;
/* Data Transfer Object class */
#XmlRootElement
public class PersonData {}
This ensures the integrity of the database model, and independently you have how you are going to display the data in a different class.

Categories