Error when de-serializing enum, com.fasterxml.jackson.databind.exc.InvalidDefinitionException - enums

I have two classes, Incident and Status. Status is an enum
Incident.class
#Data
#NoArgsConstructor
#AllArgsConstructor
public class Incident implements Serializable {
#JsonFormat(pattern = "dd/MM/yyyy HH:mm:ss", timezone = "UTC")
private Instant createdDate;
#JsonFormat(pattern = "dd/MM/yyyy HH:mm:ss", timezone = "UTC")
private Instant closeDate;
private Enum<Status> status;
}
Status.class
#AllArgsConstructor
public enum Status {
OPEN("OPEN"),
ON_GOING("ON_GOING"),
CLOSED("CLOSED");
private String status;
}
I tried to de-serialize json payload
#Test
public void deserializingTest() throws JsonProcessingException {
String payloadJson = "{" +
"\"createdDate\":\"08/06/2022 08:08:52\"," +
"\"closeDate\":\"08/06/2022 08:08:52\"," +
"\"status\":\"CLOSED\"" +
"}";
Incident actual = new ObjectMapper()
.registerModule(new JavaTimeModule())
.readValue(payloadJson, Incident.class);
Incident expected = new Incident(
InstantGenerator.generateInstantUTC(2022,6,8, 8, 8, 52),
InstantGenerator.generateInstantUTC(2022,6, 8, 8, 8, 52),
Status.OPEN);
assertThat(actual).usingRecursiveComparison().isEqualTo(expected);
}
And i get following error:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `java.lang.Enum` (no Creators, like default constructor, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
at [Source: (String)"{"createdDate":"08/06/2022 08:08:52","closeDate":"08/06/2022 08:08:52","status":"CLOSED"}"; line: 1, column: 81] (through reference chain: com.rtambun.dto.incident.Incident["status"])
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)
at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1904)
I tried to find the same error but seems no one reporting issue on enum not able to find Creator.

Related

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.

What is the ideal way to serialize and deserialize polymorphic entity attribute in spring boot?

I have an Entity class with a column attribute whose type is an abstract class. I want to serialize (object to JSON string) while saving it in the database column and deserialize it into an abstract class (which in turn converts the string to the appropriate concrete class) when it is retrieved from the database.
Here's how I accomplished it:
ProductEntity.java
#Entity
#Table(name="PRODUCT")
#Data
public class ProductEntity{
#Id
#Column(name = "ID", insertable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private BigInteger id;
#Column(name = "DESCRIPTION")
private String description;
#Column(name = "NAME")
private String name;
#Column(name = "PRODUCT_TYPE")
private String productType;
#Column(name = "PRODUCT_SPECS")
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property =
"productType") // -------------------> Map to concrete class based on productType value
#Convert(converter = ObjectConverter.class) // ------------> custom converter
private ProductSpecification productSpec;
}
NOTE : "PRODUCT_SPECS" database column is of JSON type.
ProductSpecification.java
#NoArgsConstructor
#JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS,
include = JsonTypeInfo.As.WRAPPER_OBJECT,
#JsonSubTypes({
#JsonSubTypes.Type(value = ComputerSpecification.class, name = "computer"),
#JsonSubTypes.Type(value = SpeakerSpecification.class, name = "speaker")
})
public abstract class ProductSpecification{ }
ComputerSpecification.java
#Getter
#Setter
#NoArgsConstructor
#JsonTypeName("computer")
public class ComputerSpecification extends ProductSpecification {
String memory;
String displaySize;
String processor;
#JsonCreator
public ComputerSpecification (#JsonProperty("memory") String memory,
#JsonProperty("displaysize") String displaySize,
#JsonProperty("processor") String processor){
super();
this.memory = memory;
this.displaySize = displaySize;
this.processor = processor;
}
}
SpeakerSpecification.java
#Getter
#Setter
#NoArgsConstructor
#JsonTypeName("computer")
public class SpeakerSpecification extends ProductSpecification {
String dimension;
String sensitivity;
String bassPrinciple;
String amplifierPower;
#JsonCreator
public SpeakerSpecification (#JsonProperty("sensitivity") String sensitivity,
#JsonProperty("dimension") String dimension,
#JsonProperty("bassPrinciple") String bassPrinciple,
#JsonProperty("amplifierPower") String amplifierPower){
super();
this.sensitivity = sensitivity;
this.dimension = dimension;
this.bassPrinciple = bassPrinciple;
this.amplifierPower = amplifierPower;
}
}
ObjectConverter.java
NOTE: I am using Jackson ObjectMapper for serialization and deserialization.
public class ObjectConverter implements AttributeConverter<Object, String>{
private final static Logger LOGGER = LoggerFactory.getLogger(ObjectConverter.class);
private static final ObjectMapper mapper;
static {
mapper = new ObjectMapper();
mapper.setSerializationInclusion(Include.NON_NULL);
}
#Override
public String convertToDatabaseColumn(Object attributeObject) {
if (attributeObject == null) {
return "";
}
try {
return mapper.writeValueAsString(attributeObject);
} catch (JsonProcessingException e) {
LOGGER.error("Could not convert to database column", e);
return null;
}
}
#Override
public Object convertToEntityAttribute(String dbColumnValue) {
try {
if (StringUtils.isBlank(dbColumnValue)) {
return null;
}
return mapper.readValue(dbColumnValue, ProductSpecification.class); // ----> mapped to
abstract class
} catch (Exception e) {
LOGGER.error("Could not convert to entity attribute", e);
return null;
}
}
}
Request body 1:
{
"name" : "Bose Bass Module 700 - Black- Wireless, Compact Subwoofer",
"description" : "This wireless, compact subwoofer is designed to be paired with the Bose sound
bar 700 to bring music, movies, and TV to life with Deep, dramatic bass. ",
"productSpec" : {
"sensitivity" : "90 dB",
"bassPrinciple" : "reflex",
"amplifierPower" : "700 watts",
"dimension" : "14-5/16inW x 42-13/16inH x 16-5/16inD"
}
}
This request gets saved in the database column "PRODUCT_SPECS" as :
{".SpeakerSpecification ":{"sensitivity" : "90 dB","bassPrinciple" : "reflex", "amplifierPower" :"700
watts", "dimension" : "14-5/16inW x 42-13/16inH x 16-5/16inD" }}
Now this solution works perfectly fine. The "SpeakerSpecification " key neither appears in the response of GET API call nor in the swagger doc. But having to store the type info in the database really bothers me.
Is there a better approach to this problem where I could avoid having the typeinfo (".SpeakerSpecification ") in the column value?

Timestamp converter not working in Spring Data Rest with Spanner

I'm trying to convert the input timestamp which will be in the string format to cloud timestamp with the help of a Spring Data Rest custom converter which is not working. Need an help on the same in understanding why custom converters are not invoked.
Input: http://localhost:8080/apipromocentral/promotions
RequestBody : {"startDateTime": "2019-11-07 15:53:00"}
POJO:
#ApiModel
#Data
#AllArgsConstructor
#Table(name = "PROMOTIONS")
public class Promotion {
/**
* promotion id
*/
#ApiModelProperty(notes = "Id of the Promotion", required = true)
#PrimaryKey
#Column(name = "PROMO_ID")
private String promotionId;
#ApiModelProperty(notes = "Start Date Time of a promotion", allowableValues="yyyy-MM-dd HH:mm:ss", required = true)
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
#Column(name = "START_DATE_TIME")
private Timestamp startDateTime; //java.sql.timestamp;
}
converter code
#Component
public class TimestampWriteConverter implements Converter<java.sql.Timestamp, Timestamp> {
#Override
public Timestamp convert(java.sql.Timestamp sqlTimestamp) {
//Return com.google.cloud.Timestamp;
return Timestamp.of(sqlTimestamp);
}
}
exception
"message": "FAILED_PRECONDITION: com.google.api.gax.rpc.FailedPreconditionException: io.grpc.StatusRuntimeException: FAILED_PRECONDITION: Invalid value for column START_DATE_TIME in table PROMOTIONS: Expected TIMESTAMP.",
"trace": "com.google.cloud.spanner.SpannerException: FAILED_PRECONDITION: com.google.api.gax.rpc.FailedPreconditionException: io.grpc.StatusRuntimeException: FAILED_PRECONDITION: Invalid value for column START_DATE_TIME in table PROMOTIONS: Expected TIMESTAMP.\r\n\tat com.google.cloud.spanner.SpannerExceptionFactory.newSpannerExceptionPreformatted(SpannerExceptionFactory.java:156)\r\n\tat com.google.cloud.spanner.SpannerExceptionFactory.newSpannerException(SpannerExceptionFactory.java:45)\r\n\tat com.google.cloud.spanner.SpannerExceptionFactory.newSpannerException(SpannerExceptionFactory.java:112)\r\n\tat
Looking at the documentation, looks like you need pass the TimestampWriteConverter converter to ConverterAwareMappingSpannerEntityProcessor.
#Configuration
public class ConverterConfiguration {
#Bean
public SpannerEntityProcessor spannerEntityProcessor(SpannerMappingContext spannerMappingContext) {
return new ConverterAwareMappingSpannerEntityProcessor(spannerMappingContext,
Arrays.asList(new TimestampWriteConverter()),
Arrays.asList());
}
}

Convert String to DTO in the validator of constraint

I have this field in a DTO:
private List<RoleDTO> roles;
It has a validator:
public class InternalUserValidator implements ConstraintValidator<InternalUserConstraint, Object> {
ObjectMapper mapper = new ObjectMapper();
mapper.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
private String[] rolesAsString;
private List<RoleDTO> roles;
.
.
.
#Override
public boolean isValid(final Object value, final ConstraintValidatorContext context) {
//here i get as string, for example: RoleDTO{id=1, name='ADMIN'}
rolesAsString = BeanUtils.getArrayProperty(value, rolesFieldName);//should i use also getproperty again?
//then i try to convert to arraylist:
roles = (List<RoleDTO>) mapper.convertValue(rolesAsString, RoleDTO.class);
but it gives
Cannot construct instance of model.RoleDTO (out of START_ARRAY token
at [Source: UNKNOWN; line: -1, column: -1]
So, while in debug, i also tried this:
(List<RoleDTO>) mapper.convertValue("{\"id\":5,\"name\":\"sad\"}", RoleDTO.class)
This time:
Cannot construct instance of model.RoleDTO (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('{"id":5,"name":"sad"}')
at [Source: UNKNOWN; line: -1, column: -1]
What can i do?
This is RoleDTO:
public class RoleDTO implements Serializable {
private Long id;
private String name;
//getters setters
public RoleDTO() {
}
Cannot construct instance of model.RoleDTO (out of START_ARRAY token at [Source: UNKNOWN; line: -1, column: -1]
This Error Means you are trying to deserialize JSONArray to the RoleDto object.. Which is not possible.
The rolesAsString here has value of something like "[{\"id\":5,\"name\":\"sad\"}]"
Use ObjectMapper with TypeReference like this..
mapper.readValue(rolesAsString, new TypeReference<List<RoleDTO>>() {
});

I am developing RESTful app using Spring. I want to handle case when in POST request body has wrong data type

I am using spring boot making web services and one of them taking object from :
public class GroupRouteRequestDTO {
private Long groupID;
private String userToken;
private Long pageIndex;
private Long pageSize;
private String search;
}
class
in postman I make request with body
{
"groupID":"11AA",
"userToken": "9a",
"pageIndex":0,
"pageSize":12,
"search":"A"
}
I get
{
"timestamp": 1557340656686,
"status": 400,
"error": "Bad Request",
"exception": "org.springframework.http.converter.HttpMessageNotReadableException",
"message": "Could not read document: Can not deserialize value of type java.lang.Long from String \"11AA\": not a valid Long value\n at [Source: java.io.PushbackInputStream#1226796e; line: 2, column: 12] (through reference chain: com.ntgclarity.ngnts.datatransferobject.GroupRouteRequestDTO[\"groupID\"]); nested exception is com.fasterxml.jackson.databind.exc.InvalidFormatException: Can not deserialize value of type java.lang.Long from String \"11AA\": not a valid Long value\n at [Source: java.io.PushbackInputStream#1226796e; line: 2, column: 12] (through reference chain: com.ntgclarity.ngnts.datatransferobject.GroupRouteRequestDTO[\"groupID\"])",
"path": "/toDoList/Employee"
}
this response from postman
And the web service is
#PostMapping
#RequestMapping("/Employee")
#ResponseStatus(HttpStatus.CREATED)
#PreAuthorize("hasAuthority('ToDoList_Access')")
public Object getEmployeesRoutList(#Valid #RequestBody GroupRouteRequestDTO groupRouteRequest,HttpServletRequest request)
throws EntityNotFoundException {
return toDoListService.getEmployeesRoutList(groupRouteRequest,request);
}
Question : can I make customize error msg from the web service to handle when the body of the request has wrong datatype?
I solve the issue by adding this method in controller class
#ResponseBody
public ResponseEntity<Object> MessageNotReadableException(HttpMessageNotReadableException ex,HttpServletResponse response){
ex.printStackTrace();
return new ResponseEntity<Object>("Bad Request Please Check Your Inputs",HttpStatus.BAD_REQUEST);
}```
You can use Bean Validation. Refer link
For eg :
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import javax.validation.constraints.Email;
public class User {
#NotNull(message = "Name cannot be null")
private String name;
#AssertTrue
private boolean working;
#Size(min = 10, max = 200, message
= "About Me must be between 10 and 200 characters")
private String aboutMe;
#Min(value = 18, message = "Age should not be less than 18")
#Max(value = 150, message = "Age should not be greater than 150")
private int age;
#Email(message = "Email should be valid")
private String email;
// standard setters and getters
}

Resources