Jackson Inheritance - spring-boot

Can't seem to figure this out. I keep getting various errors so I'll just write this with the current error I'm getting from Jackson.
public class ResponseDetail {
private Response response;
}
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME)
#JsonSubTypes({
#JsonSubTypes.Type(value = ResponseTypeOne.class, name = "ResponseTypeOne"),
#JsonSubTypes.Type(value = ResponseTypeTwo.class, name = "ResponseTypeTwo"),
#JsonSubTypes.Type(value = ResponseTypeThree.class, name = "ResponseTypeThree")
})
#JsonIgnoreProperties(ignoreUnknown = true)
public abstract class Response {
}
In other packages I have these three:
public class ResponseTypeOne extends Response {
private Integer status;
}
public class ResponseTypeTwo extends Response {
private String message;
}
public class ResponseTypeThree extends Response {
private String value;
}
Error:
Caused by: com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Missing type id when trying to resolve subtype of [simple type, class com.services.models.Response]: missing type id property '#type' (for POJO property 'response')
I have tried various iterations of this #JsonTypeInfo with various includes and various property's also with Id.CLASS with no luck.

You need to declare how the type should be recognized.
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "#ttype")
#JsonSubTypes({
#JsonSubTypes.Type(value = ResponseTypeOne.class, name = "ResponseTypeOne"),
#JsonSubTypes.Type(value = ResponseTypeTwo.class, name = "ResponseTypeTwo"),
#JsonSubTypes.Type(value = ResponseTypeThree.class, name = "ResponseTypeThree")
})
#JsonIgnoreProperties(ignoreUnknown = true)
public abstract class Response {
#JsonProperty("#ttype")
public abstract String getChildType();
}
And in child types do like below:
#JsonTypeName("ResponseTypeOne")
public class ResponseTypeOne extends Response {
#Override
public String getChildType() {
return "ResponseTypeOne";
}
}
And incoming json should be like below to enable jackson to find the correct child implementation:
{
//some attributes of child Response
"#ttype": "ResponseTypeOne"
}

Related

spring Jaxb2XmlDecoder fails to decode when using inheritance with #XmlSeeAlso (polymorphism)

While introducing WebClient into my current project I ran into the issue that the call
response.bodyToMono(RealEstate::class.java)
causes the error
org.springframework.core.codec.DecodingException: Could not unmarshal XML to class test.RealEstate; nested exception is javax.xml.bind.UnmarshalException
- with linked exception:
[com.sun.istack.SAXParseException2; lineNumber: 2; columnNumber: 1; Unable to create an instance of test.RealEstate]
at org.springframework.http.codec.xml.Jaxb2XmlDecoder.unmarshal(Jaxb2XmlDecoder.java:242)
Suppressed: The stacktrace has been enhanced by Reactor, refer to additional information below:
We have the following simplified class hierarchy:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "RealEstate", namespace = "http://anything.you.want", propOrder = {"address"})
#XmlSeeAlso({House.class, Apartment.class})
public abstract class RealEstate {
protected String address;
public String getAddress() { return address; }
public void setAddress(String address) { this.address = address; }
}
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "Apartment", propOrder = {"livingArea"}, namespace = "http://anything.you.want")
#XmlRootElement(name = "apartment", namespace = "http://anything.you.want")
public class Apartment extends RealEstate {
private String livingArea;
public String getLivingArea() {
return livingArea;
}
public void setLivingArea(String livingArea) {
this.livingArea = livingArea;
}
}
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "House", propOrder = {"plotArea"}, namespace = "http://anything.you.want")
#XmlRootElement(name = "house", namespace = "http://anything.you.want")
public class House extends RealEstate {
private String plotArea;
public String getPlotArea() {
return plotArea;
}
public void setPlotArea(String plotArea) {
this.plotArea = plotArea;
}
}
One possible response body to be decoded is
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<objects:house xmlns:objects="http://anything.you.want">
<address>Somewhere around the corner</address>
<plotArea>small</plotArea>
</objects:house>
First question would be if there is something wrong with how JAXB is used?
In case there is nothing really wrong with that I go on to my other question.
Looking into the Decoder there is the logic to call the Unmarshaller
Unmarshaller unmarshaller = initUnmarshaller(outputClass);
XMLEventReader eventReader = StaxUtils.createXMLEventReader(events);
if (outputClass.isAnnotationPresent(XmlRootElement.class)) {
return unmarshaller.unmarshal(eventReader);
}
else {
JAXBElement<?> jaxbElement = unmarshaller.unmarshal(eventReader, outputClass);
return jaxbElement.getValue();
}
Since RealEstate is not annotated as XML root element the else block is executed which causes the error. Using the other method without handing over the target class would work.
Now the question would be why is that not working? I assume using XMLSeeAlso should cause the unmarshaller to create the right type of real estate, shouldn't it?
As a side note I also looked into how the RestTemplate solved the task and there it boils down to the MarshallingHttpMessageConverter doing
Object result = this.unmarshaller.unmarshal(source);
if (!clazz.isInstance(result)) {
throw new TypeMismatchException(result, clazz);
}
return result;
It seems that the decoder differentiates for some reason while the message converter is doing the type check after unmarshalling.
And since it might be relevant how the Jaxb2Marshaller is setup
Jaxb2Marshaller().apply {
setPackagesToScan(
RealEstate::class.java.getPackage().name,
)
setCheckForXmlRootElement(false)
setSupportJaxbElementClass(true)
}

Mapstruct throw exception when using both Mapping with and without qualifiedByName option

I have an interface mapper for profile:
#Mapper(componentModel = "spring", builder = #Builder(disableBuilder = true))
public interface SellerProfileMapper {
#Mapping(target = "companyProfileDTO.companyProfileId", source = "id")
#Mapping(target = "companyProfileDTO.companyPicture", source = "company.picture", qualifiedByName = "buildBase64EncodingProfilePicture")
SellerProfileResponseDTO entityToSellerProfileResponseDTO(SellerProfileV2 sellerProfileV2);
#Named("buildBase64EncodingProfilePicture")
default String buildBase64EncodingProfilePicture(Image picture) {
return ofNullable(picture)
.map(image -> Base64.getEncoder().encodeToString(image.getContent()))
.orElse(null);
}
The problem is that the implementation for this mapper can only work if I remove either
#Mapping(target = "companyProfileDTO.companyProfileId", source = "id")
or this
#Mapping(target = "companyProfileDTO.companyPicture", source = "company.picture", qualifiedByName = "buildBase64EncodingProfilePicture")
then the implementation is generated! The question is why? Did I miss anything?
This is the DTO:
#Data
#Builder
#AllArgsConstructor
public class SellerProfileResponseDTO {
private CompanyProfileDTO companyProfileDTO;
private SellerProfileDTO sellerProfileDTO;
}
#NoArgsConstructor
#AllArgsConstructor
#Data
#JsonInclude(JsonInclude.Include.NON_NULL)
#Schema(description = "Being used as a based class for Adviser, Buyer and Seller company profile.")
public class CompanyProfileDTO {
#Schema(description = "Company logo as a base64 img", type = "String")
private String companyPicture;
private String companyPictureFileName;
private String companyProfileId;
private String companyId;
}
this is the entity I want to map:
public class SellerProfileV2 {
private String id;
private String alias;
private Company company;
...
}
The error that I get is:
Internal error in the mapping processor: java.lang.RuntimeException: org.ma pstruct.ap.shaded.freemarker.core.InvalidReferenceException: The following has evaluated to null or missing:
public interface SellerProfileMapper {
^
==> ext.targetBeanName [in template "org/mapstruct/ap/internal/model/assignment/UpdateWrapper.ftl" at line 31, column 12]
----
Tip: It's the step after the last dot that caused this error, not those before it.
----
Tip: If the failing expression is known to be legally refer to something that's null or missing, either specify a default value like myOptionalVar!myDefault, or use <#if myOptionalVar??>when-present<#else>when-missing</#if>. (Thes
e only cover the last step of the expression; to cover the whole expression, use parenthesis: (myOptionalVar.foo)!myDefault, (myOptionalVar.foo)?
This is the generated class for the mapper:
#Component
public class SellerProfileMapperImpl implements SellerProfileMapper {
#Override
public SellerProfileResponseDTO entityToSellerProfileResponseDTO(SellerProfileV2 sellerProfileV2) {
if ( sellerProfileV2 == null ) {
return null;
}
CompanyProfileDTO companyProfileDTO = null;
CompanyProfileDTO companyProfileDTO = null;
if ( sellerProfileV2.getCompany() != null ) {
if ( FreeMarker template error:
The following has evaluated to null or missing:
==> ext.targetBeanName [in template "org/mapstruct/ap/internal/model/assignment/UpdateWrapper.ftl" at line 31, column 12]
----
Tip: It's the step after the last dot that caused this error, not those before it.

Sending #Value annotated fields to a DTO layer returns null

I have a class which is composed of 2 different objects :
public class MyClass{
private OptionClass optionClass;
private ConstantClass constantClass;
public DocumentToSignRestRequest(OptionClass optionClass, ConstantClass constantClass) {
this.optionClass= optionClass;
this.constantClass= constantClass;
}
}
My first class is a classic POJO. My second class retrieve values from the application.properties file.
public class ConstantClass {
#Value("${api.url}")
private String hostName;
#Value("${sign.path}")
private String pathStart;
public ConstantClass () {
this.hostName= getHostName();
this.path = getPath();
}
I map MyClass with MyClassDto in order to call a service.
#PostMapping(
value="/sign",
consumes = { MediaType.APPLICATION_XML_VALUE, MediaType.APPLICATION_JSON_VALUE },
produces = { MediaType.APPLICATION_XML_VALUE, MediaType.APPLICATION_JSON_VALUE }
)
public MyClassRest prepareDocument(#RequestBody DocumentToPrepare documentToPrepare) throws Exception {
MyClassRest returnValue = new MyClassRest ();
ModelMapper modelMapper = new ModelMapper();
MyClassDto myClassDto = modelMapper.map(documentToPrepare, MyClassDto .class);
DocumentDto signedDocument = documentService.signDocument(documentDto);
returnValue = modelMapper.map(signedDocument, DocumentRest.class);
return returnValue;
}
My DTO class work fine and retrieve the OptionClass datas, but concerning the second Class, i obtain null as value, while i try to print it out in the service layer.
Your ConstantClass should be a Bean or a Component (as #cassiomolin says in comments)
#Component
public class ConstantClass {
private String hostName;
private String pathStart;
public ConstantClass (#Value("${api.url}") String url, #Value("${sign.path}") String path ) {
this.hostName = url;
this.pathStart = path;
}
// getters...
Then you can easily inject this component in your Controller and use it.
#Controller
public class YourController(){
private ConstantClass constantClass;
public YourController(ConstantClass constantClass){
this.constantClass = constantClass;
}
#PostMapping("...")
public MyClass post(.....){
.....
MyClass myclass = new MyClass(this.constantClass,...)
.....
}
}
note that Spring can autowire #Value and #Component, ... via the constructor; that can be very useful when you do unit-testing

Marshal list of objects of different types with JAXB

I work with Spring and JAXB and want to marshal a list of different DTO Objects so
the required XML response should be like :
<root>
<dto_list>
<dto1>
<name>xxx</name>
</dto1>
<dto2>
<location>xxx</location>
</dto2>
</dto_list>
</root>
Assuming the Class Objects are:
#XmlRootElement(name = "Dto1")
#XmlAccessorType(XmlAccessType.NONE)
public class Dto1 {
#XmlElement
private String name;
// setter/getters
}
and
#XmlRootElement(name = "Dto2")
#XmlAccessorType(XmlAccessType.NONE)
public class Dto2 {
#XmlElement
private String location;
// setter/getters
}
and the wrapper class:
#XmlRootElement(name = "root")
#XmlAccessorType(XmlAccessType.NONE)
public class DTOsWrapper {
private List<Object> dto;
public void setDto(List<Object> dto) {
this.dto = dto;
}
#XmlElementWrapper(name = "dto_list")
#XmlElements({
#XmlElement(name = "dto1", type = Dto1.class),
#XmlElement(name = "dto2", type = Dto2.class)
})
public List<Object> getDto() {
return dto;
}
}
and the endpoint:
#RestController
public class DTOEndpoint {
#Autowired
private IDTOService service;
#RequestMapping(value = "/restxml", produces = "application/xml")
public Object retrieveAllDTOs() {
DTOsWrapper o = service.findDtos(); //returns a DTOsWrapper obj of a list containing Dto objs, i.e Dto1, Dto2 etc
return o;
}
I get {"error": "org.springframework.http.converter.HttpMessageConversionException: Could not instantiate JAXBContext for class [class <>to.DTOsWrapper]: 1 counts of IllegalAnnotationExceptions; anyone have a clue?

JPA repository fail with good name of property and works with wrong name

I have a class with property named isChecked with is boolean type. In Jpa repository I wrote a method to find all rows which has isChecked = false;
public interface ReservationReminderRepository extends JpaRepository<ReservationReminder, Integer> {
ReservationReminder findByReservationReminderId(Integer id);
//#Query("select r from ReservationReminder r where r.isChecked = :checked")
List<ReservationReminder> findByChecked(boolean checked);
}
While I tried to call a method findByChecked() in Jpa reporistory everything works, but when I tried to run a method with the proper named of property - as it is in jpa doc findByIsChecked() I got strange fails:
Caused by: java.lang.IllegalArgumentException: Unable to locate Attribute with the the given name [isChecked] on this ManagedType [com.carwash.domains.ReservationReminder]
at org.hibernate.jpa.internal.metamodel.AbstractManagedType.checkNotNull(AbstractManagedType.java:128)
at org.hibernate.jpa.internal.metamodel.AbstractManagedType.getAttribute(AbstractManagedType.java:113)
at org.springframework.data.jpa.repository.query.QueryUtils.toExpressionRecursively(QueryUtils.java:566)
at org.springframework.data.jpa.repository.query.JpaQueryCreator$PredicateBuilder.getTypedPath(JpaQueryCreator.java:334)
at org.springframework.data.jpa.repository.query.JpaQueryCreator$PredicateBuilder.build(JpaQueryCreator.java:277)
at org.springframework.data.jpa.repository.query.JpaQueryCreator.toPredicate(JpaQueryCreator.java:182)
at org.springframework.data.jpa.repository.query.JpaQueryCreator.create(JpaQueryCreator.java:109)
at org.springframework.data.jpa.repository.query.JpaQueryCreator.create(JpaQueryCreator.java:49)
at org.springframework.data.repository.query.parser.AbstractQueryCreator.createCriteria(AbstractQueryCreator.java:109)
at org.springframework.data.repository.query.parser.AbstractQueryCreator.createQuery(AbstractQueryCreator.java:88)
at org.springframework.data.repository.query.parser.AbstractQueryCreator.createQuery(AbstractQueryCreator.java:73)
at org.springframework.data.jpa.repository.query.PartTreeJpaQuery$QueryPreparer.<init>(PartTreeJpaQuery.java:118)
at org.springframework.data.jpa.repository.query.PartTreeJpaQuery$CountQueryPreparer.<init>(PartTreeJpaQuery.java:241)
at org.springframework.data.jpa.repository.query.PartTreeJpaQuery.<init>(PartTreeJpaQuery.java:68)
at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$CreateQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:103)
... 104 more
Can anyone tell me why I received that kind of fail? How the method name would looks like when I'd like to check with property checkedDate?
package com.carwash.domains;
import javax.persistence.*;
import java.util.Date;
/**
* Created by mbi on 01.03.2017.
*/
#Entity
public class ReservationReminder {
private int reservationReminderId;
private Reservation reservation;
private boolean isChecked;
private Date checkedDate;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
public int getReservationReminderId() {
return reservationReminderId;
}
public void setReservationReminderId(int reservationReminderId) {
this.reservationReminderId = reservationReminderId;
}
#OneToOne(mappedBy = "reservationReminder")
public Reservation getReservation() {
return reservation;
}
public void setReservation(Reservation reservation) {
this.reservation = reservation;
}
public boolean getChecked() {
return isChecked;
}
public void setChecked(Boolean checked) {
isChecked = checked;
}
public Date getCheckedDate() {
return checkedDate;
}
public void setCheckedDate(Date checkedDate) {
this.checkedDate = checkedDate;
}
#Override
public String toString() {
return "ReservationReminder{" +
"reviewId=" + reservationReminderId +
", isChecked=" + isChecked +
", checkedDate=" + checkedDate +
'}';
}
public ReservationReminder() {
}
public ReservationReminder(Boolean isChecked, Date checkedDate) {
this.isChecked = isChecked;
this.checkedDate = checkedDate;
}
public ReservationReminder(int reservationReminderId, Reservation reservation, boolean isChecked, Date checkedDate) {
this.reservationReminderId = reservationReminderId;
this.reservation = reservation;
this.isChecked = isChecked;
this.checkedDate = checkedDate;
}
}
It seems that the problem is related to the naming of that property.
As you are telling Spring to look for findByChecked and the property name is isChecked.
You can try to use findByIsChecked and change the getter to isChecked.
But actually i would change the property to checked, getter to isChecked and leave the jpa query method as it is.

Resources