Get Derived DTO From Base Class Request Body DTO - java

I try to get derived class fields from methods response body. Request body parameter is type of base class. Request comes with derived class fields but I can't cast it to derived class.
Here is my controller method and DTO classes:
Method:
#PostMapping("/{code}")
public ResponseEntity<PromotionDto> createPromotion(#PathVariable String code, #RequestBody PromotionDto promotion){
if(PromotionTypeEnum.ORDER_THRESHOLD_DISCOUNT.equals(promotion.getPromotionType())) {
promotionService.createPromotion(orderThresholdDiscountPromotionConverter.toEntity((OrderThresholdDiscountPromotionDto)promotion));
}
return ResponseEntity.ok(promotion);
}
Base class DTO:
import dto.base.BaseDto;
import promotionservice.PromotionTypeEnum;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
#Data
#EqualsAndHashCode(callSuper = true)
#ToString(callSuper = true)
public class PromotionDto extends BaseDto {
private String code;
private String title;
private String description;
private PromotionTypeEnum promotionType;
}
Derived class DTO:
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
#Data
#EqualsAndHashCode(callSuper = true)
#ToString(callSuper = true)
public class OrderThresholdDiscountPromotionDto extends PromotionDto {
private Double thresholdTotal;
private Double discountPrice;
private String messageFired;
private String messageCouldHaveFired;
}
Request JSON is:
{
"code":"qwewe",
"title":"qwewe",
"description":"qwewe",
"promotionType":"ORDER_THRESHOLD_DISCOUNT",
"thresholdTotal":1.3,
"discountPrice":"12.5",
"messageFired":"qwewe",
"messageCouldHaveFired":"qwewe"
}
as result, service returns error:
{
"type": "https://www.jhipster.tech/problem/problem-with-message",
"title": "Internal Server Error",
"status": 500,
"detail": "promotion.PromotionDto cannot be cast to promotion.OrderThresholdDiscountPromotionDto",
"path": "/api/promotionresults/qwewe",
"message": "error.http.500"
}
My question is: is there any way, library, annotation etc. to get the
derived class instance from request ?

Use Jackson inheritance feature. Annotate PromotionDto class as below:
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "promotionType")
#JsonSubTypes({
#Type(value = OrderThresholdDiscountPromotionDto.class, name = "ORDER_THRESHOLD_DISCOUNT"),
})
class PromotionDto {
and remove:
private PromotionTypeEnum promotionType;
property. It will be handled automatically by Jackson. In controller you will be able to use instanceof.

What are you trying to do is you are trying typecast the Parent into a child which is known as Downcasting. This is only valid when you have the Parent as an instance of child. In your case, PromotionDto should be an instance of OrderThresholdDiscountPromotionDto.
Please refer below example:
public class PromotionDto {
private String code;
private String title;
private String description;
public static void main(String[] args) {
PromotionDto promotionDto = new OrderThresholdDiscountPromotionDto();
PromotionDto promotionDto_2 = new PromotionDto();
//Valid downcasting
OrderThresholdDiscountPromotionDto subClass1 = (OrderThresholdDiscountPromotionDto)promotionDto;
//Invalid down casting
OrderThresholdDiscountPromotionDto subClass2 = (OrderThresholdDiscountPromotionDto)promotionDto_2;
}
}
class OrderThresholdDiscountPromotionDto extends PromotionDto {
private Double thresholdTotal;
private Double discountPrice;
private String messageFired;
private String messageCouldHaveFired;
}

Related

Define Jackson Wrapper Name from its Inner Class

I'm creating a common class to standardize my JSON structure as written below,
public class Wrapper<T> {
private SoaHeader soaHeader;
private T payload;
}
public class PayloadFoo {
private String foo;
}
public class PayloadBar {
private String bar;
}
public class main(){
var foo = new Wrapper<PayloadFoo>();
var bar = new Wrapper<PayloadBar>();
}
Then later the expected JSON result for both foo and bar are
{
"foo": {
"soaHeader": {},
"payload": {
"foo": ""
}
}
}
and
{
"bar": {
"soaHeader": {},
"payload": {
"bar": ""
}
}
}
Can Jackson do such task by put either #JsonTypeName or #JsonRootName annotation on the PayloadFoo and PayloadBar classes? or any suggestion how can I achieve this? Thankyou
Jackson can handle this by using the #JsonTypeName annotation on the PayloadFoo and PayloadBar classes and the #JsonTypeInfo annotation on the Wrapper class.
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
public class JsonSubTypesExample {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper()
.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
public static void main(String[] args) throws JsonProcessingException {
Wrapper<PayloadFoo> payloadFooWrapper = new Wrapper<>(new SoaHeader(), new PayloadFoo("foo"));
Wrapper<PayloadBar> payloadBarWrapper = new Wrapper<>(new SoaHeader(), new PayloadBar("bar"));
System.out.println(OBJECT_MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(payloadFooWrapper));
System.out.println(OBJECT_MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(payloadBarWrapper));
}
#JsonSubTypes({
#JsonSubTypes.Type(value = PayloadFoo.class, name = "foo"),
#JsonSubTypes.Type(value = PayloadBar.class, name = "bar"),
})
#Data
#ToString
#NoArgsConstructor
#AllArgsConstructor
public static class Wrapper<T> {
private SoaHeader soaHeader;
private T payload;
}
#JsonTypeName("foo")
#Data
#ToString
#NoArgsConstructor
#AllArgsConstructor
public static class PayloadFoo {
private String foo;
}
#JsonTypeName("bar")
#Data
#ToString
#NoArgsConstructor
#AllArgsConstructor
public static class PayloadBar {
private String bar;
}
#Data
#ToString
public static class SoaHeader {
}
}

Why are some of the variables in POJO equal to null after converting JSON RESTful Webservice?

I am consuming a RESTful webservice that returns a JSON payload. I can successfully consume the RESTful webservice and manage to populate some of the POJO attributes with JSON data. However, some other attributes are null when they are supposed to contain a value. How can I ensure that there are no more nulls?
I have defined 4 POJO classes. I have so far debugged by systematically by testing the variables for each class. This is using Springboot 2.2.0 and Jackson-databind.
The JSON schema I am trying to consume:
{
"items":[
{
"timestamp":"2019-09-18T16:42:54.203Z",
"carpark_data":[
{
"total_lots":"string",
"lot_type":"string",
"lots_available":"string"
}
]
}
]
}
For the above, I defined 4 classes:
public class Response {
#JsonProperty
private List<items> i;
#JsonIgnoreProperties(ignoreUnknown = true)
public class items {
private String timestamp;
private List<carpark_data> cpd;
#JsonIgnoreProperties(ignoreUnknown = true)
public class carpark_data {
private List<carpark_info> cpi;
private String carpark_number;
private String update_datetime;
#JsonIgnoreProperties(ignoreUnknown = true)
public class carpark_info {
private int total_lots;
private String lot_type;
private int lots_available;
When I run the below in Spring boot Main: I get null. Is my POJO modeling OK?
Response resp = restTemplate.getForObject("")
c = resp.getItems().get(0).getCarpark_data().get(0);
log.info("The last update time for the car park data = " +
c.getUpdateDatetime());
Your model does not fit to JSON payload. If we assume that JSON payload has a structure like below:
{
"items": [
{
"timestamp": "2019-09-18T16:42:54.203Z",
"carpark_data": [
{
"total_lots": "1000",
"lot_type": "string",
"lots_available": "800"
}
]
}
]
}
We can deserialise it as below:
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.File;
import java.util.List;
public class JsonApp {
public static void main(String[] args) throws Exception {
File jsonFile = new File("./resource/test.json").getAbsoluteFile();
ObjectMapper mapper = new ObjectMapper();
Response response = mapper.readValue(jsonFile, Response.class);
System.out.println(response.getItems().get(0).getData().get(0));
}
}
class Response {
private List<Item> items;
//getters, setters, toString
}
class Item {
private String timestamp;
#JsonProperty("carpark_data")
private List<CarParkInfo> data;
//getters, setters, toString
}
class CarParkInfo {
#JsonProperty("total_lots")
private int totalLots;
#JsonProperty("lot_type")
private String lotType;
#JsonProperty("lots_available")
private int lotsAvailable;
//getters, setters, toString
}
Above code prints:
CarParkInfo{totalLots=1000, lotType='string', lotsAvailable=800}
Hope you find the solution.
It is in POJO, you need to check the fieldName and object structure.
Seeing the Json above, your response model returns list of items and in each item you have list of carpark_data. So, basic modelling should be like this. And you can include respective setter and getter.
public class Response {
#JsonProperty
private List<items> items;
#JsonIgnoreProperties(ignoreUnknown = true)
public class items {
private String timestamp;
private List<carpark_data> carpark_data;
#JsonIgnoreProperties(ignoreUnknown = true)
public class carpark_data {
private int total_lots;
private String lot_type;
private int lots_available;
}
You need to have fields name in POJO class same in the Json response or you can set JsonProperty for that field. Like this
#JsonProperty("items")
private List<items> i;
#JsonProperty("carpark_data")
private List<carpark_data> cpd;

Spring mapper with Java Map

I've been struggling with the following issue for a few hours now, and I can't figure it out how to make it work:
Spring mapper, in order to convert DB response to DTO:
#Mapper(componentModel = "spring")
public interface ITeamResponseToDtoMapper {
TeamResponseDTO toDto(TeamResponse teamResponse);
}
TeamResponse class:
#Data
#NoArgsConstructor
public class TeamResponse {
private Map<String, List<NameAndType>> teamList;
}
NameAndType class:
#Data
#NoArgsConstructor
#AllArgsConstructor(access = AccessLevel.PUBLIC)
public class NameAndType{
private String name;
private String type;
private String team;
}
TeamResponseDTO class:
#Data
#NoArgsConstructor
public class TeamResponseDTO {
private Map<String, List<NameAndTypeDTO >> teamList;
}
NameAndTypeDTO class:
#Data
#NoArgsConstructor
#AllArgsConstructor(access = AccessLevel.PUBLIC)
public class NameAndTypeDTO {
private String name;
private String type;
private String team;
}
Basically, 'NameAndType' and 'NameAndTypeDTO' are the same, why it fails to do the conversion?
error: Can't map property "java.util.Map<java.lang.String,java.util.List<com.microservices.teamservice.dataobjects.NameAndType>> teamList" to "java.util.Map<java.lang.String,java.util.List<com.microservices.teamservice.api.dataobjects.NameAndTypeDTO>> teamList". Consider to declare/implement a mapping method:
I think you need to explicit add methods to map the whole chain of classes. On your example the following should work:
#Mapper(componentModel = "spring")
public interface ITeamResponseToDtoMapper {
TeamResponseDTO toDto(TeamResponse teamResponse);
List<NameAndTypeDTO> natListToDTO(List<NameAndType> natList);
NameAndTypeDTO nameAndTypeToDTO(NameAndType nameAndType);
}
regards,
WiPu

JAXB multiple #XmlRootElement

I have a class annotated as follows:
#XmlRootElement(name="response")
#XmlType(propOrder={"paymentid",
"result",
"responsecode",
"authorizationcode",
"merchantorderid",
"rrn",
"cardcountry",
"cardtype"})
public class MOTOResponseIn {
...
}
The root element of the mapped XML could be also be error beside
response.
How can I annotate the class so that both root elements are allowed?
In this case #XmlRootElement can not be used.
You have to use ObjectFactory.
The #XmlElementDecl annotation is used to represent root elements that correspond to named complex types. It is placed on a factory method in a class annotated with #XmlRegistry (when generated from an XML schema this class is always called ObjectFactory). The factory method returns the domain object wrapped in an instance of JAXBElement
Hope this url will help.
https://dzone.com/articles/jaxb-and-root-elements
With a single class and #XmlRootElement it is not possible.
However, in case you don't want to mess with ObjectFactory, for a quick workaround you can use abstract and concrete classes. (Assuming the only difference is the root element)
Example:
#XmlAccessorType(XmlAccessType.FIELD)
public abstract class MOTOabstract{
#XmlAttribute
private String paymentid;
#XmlAttribute
private String result
#XmlAttribute
private String responsecode;
...
}
#XmlRootElement(name="response")
#XmlAccessorType(XmlAccessType.FIELD)
public class MOTOresponse extends MOTOabstract{}
#XmlRootElement(name="error")
#XmlAccessorType(XmlAccessType.FIELD)
public class MOTOerror extends MOTOabstract{}
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "", propOrder = {
"id",
"name",
"serviceAttrs"
})
#XmlSeeAlso({ AddGroup.class, AddGroupRequest.class })
public class AddGroupAbstract {
#XmlElement(required = true)
protected String id;
#XmlElement(required = true)
protected String name;
#XmlElement(required = true)
protected ServiceAttrs serviceAttrs;
...
}
#XmlRootElement(name = "addGroup")
public class AddGroup extends AddGroupAbstract {}
#XmlRootElement(name = "addGroupRequest")
public class AddGroupRequest extends AddGroupAbstract {}
#Endpoint
public class GroupEndpoint {
private final GroupService groupService;
private final ServiceService serviceService;
private final RestTemplate restTemplate;
public GroupEndpoint(GroupService groupService, ServiceService serviceService, RestTemplate restTemplate) {
this.groupService = groupService;
this.serviceService = serviceService;
this.restTemplate = restTemplate;
}
#PayloadRoots({
#PayloadRoot(namespace = SoapConstants.NAMESPACE_ACCOUNT_URI, localPart = "addGroup"),
#PayloadRoot(namespace = SoapConstants.NAMESPACE_ACCOUNT_URI, localPart = "addGroupRequest")
})
#ResponsePayload
public AddGroupResponse addGroup(#RequestPayload AddGroupAbstract request) {
AddGroupResponse response = new AddGroupResponse();
...
}
}

SimpleXML framework: element with element or text

I must to parse XML which can have two forms:
<Command><Variable/></Command>
or:
<Command>some text</Command>
How can I do this? When I try to declare both #Element and #Text in class responsible for Command parsing then exception is thrown when I try to parse XML to instance of this class.
My current version of code:
#Root(name = "Command", strict = false)
public class AppCommand {
#Element(name = "Variable", required = false)
#Getter
private Variable variable;
#Text(required = false)
#Getter
private String content;
}
And exception is: Text annotation #org.simpleframework.xml.Text(required=false, empty=, data=false) on field 'content' private java.lang.String com.example.AppCommand.content used with elements in class com.example.AppCommand
My solution (not beautiful, but works and doesn't require much work to implement):
private static class SerializerWithPreprocessor extends Persister {
public SerializerWithPreprocessor(RegistryMatcher matcher, Format format) {
super(matcher, format);
}
#Override
public <T> T read(Class<? extends T> type, String source) throws Exception {
source = source.replaceFirst("<Command (.*)>([[\\w||[+=]]&&[^<>]]+)</Command>", "<Command $1><Content>$2</Content></Command>");
return super.read(type, source);
}
}
So I just created new Serializer class. This class use regular expressions to change Text element inside Command into normal Element. Then I can use:
#Root(name = "Command", strict = false)
public class AppCommand {
#Element(name = "Variable", required = false)
#Getter
private Variable variable;
#Element(name = "Content", required = false)
#Getter
private String content;
}
and during deserialization everything works like I wanted to.
Yes, Simple can't deal with this.
Command.java:
import org.simpleframework.xml.Element;
import org.simpleframework.xml.Root;
import org.simpleframework.xml.Text;
#Root
public class Command {
#Element(required = false, name = "Variable")
private Variable variable;
#Text(required = false)
private String text;
}
Variable.java:
class Variable {
}
SOPlayground.java:
import org.simpleframework.xml.Serializer;
import org.simpleframework.xml.core.Persister;
public class SOPlayground {
public static void main(String[] args) throws Exception {
Serializer serializer = new Persister();
String xml1 = "<Command><Variable/></Command>";
String xml2 = "<Command>some text</Command>";
serializer.validate(Command.class, xml1);
serializer.validate(Command.class, xml2);
}
}
This does compile but it does not run:
Exception in thread "main" org.simpleframework.xml.core.TextException: Text annotation #org.simpleframework.xml.Text(data=false, required=false, empty=) on field 'text' private java.lang.String de.lhorn.so.Command.text used with elements in class de.lhorn.so.Command
It looks like can not have both #Element and #Text members.

Categories