Post request transform int field to string automatically - java

I'm doing a dummy app of a hostpital. The problem I'm having is that, I'm trying to verify that when a Patient is created, the fields passed are of the correct type, but whenever I POST an Int in a String field, it doesn't fail and just transform the Int to String. The field I'm trying to make fail is "surname", which by the definition of the Patient class, is a String.
If I do this (I pass a number to the "surname" field):
{
"name": "John",
"surname": 43,
"sickness": "headache"
}
It just transforms 43 into a String by the time its in the Controller method.
Here we have the Patient object:
#Data
#Entity
#NoArgsConstructor
#AllArgsConstructor
public class Patient implements Serializable {
private static final long serialVersionUID = 4518011202924886996L;
#Id
//TODO: posible cambiar luego la generationType
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "patient_id")
private Long id;
#Column(name = "patient_name")
#JsonProperty(required = true)
private String name;
#Column(name = "patient_surname")
#JsonProperty(required = true)
private String surname;
#Column(name = "patient_sickness")
#JsonProperty(required = true)
private String sickness;
}
And this is the controller class:
#Controller
#Path("/patient")
#Produces(MediaType.APPLICATION_JSON + ";charset=utf-8")
public class PatientController {
#POST
#Path("")
public ResponseEntity<Object> postPatient(final Patient patient) {
ResponseEntity<Object> createdPatient = patientBusiness.createPatient(patient);
return new ResponseEntity<Patient>(createdPatient.getBody(), createdPatient.getStatusCode());
}
EDIT 1:
Following the "clues" and closing the circle of attention, I tried modifying the ObjectMapper, but my configuration isn't applying. I'm still getting the error from above.
This is the config class:
#Configuration
public class JacksonConfig {
#Bean
#Primary
public ObjectMapper getModifiedObjectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(MapperFeature.ALLOW_COERCION_OF_SCALARS, false);
mapper.coercionConfigFor(LogicalType.Integer).setCoercion(CoercionInputShape.String, CoercionAction.Fail);
return mapper;
}
}
Even added the property to the application.yml, but still nothing:
spring:
jackson:
mapper:
allow-coercion-of-scalars: false
Any help is appreciated. Thx.

In the end I referred to this post to do a deserializer and a module to just have it along all the program, not just the field I want not to be transformed.
Disable conversion of scalars to strings when deserializing with Jackson

Related

I am trying to POST a JSON array to a spring-boot controller but nothing happens or even shows up in the logs

ISSUE:
I am trying to POST to an array to a controller but nothing seems to be happening, there is no info in the logs or on the terminal
I have a JSON array as follows
[
{
"artifact_id": 10,
"session_id":45,
"user_id": "user99"
}
]
I am trying to use this with a .saveAll() from my CRUD repo but nothing seems to happen it does not even seem to print my System.out.print("test"); message.
My #Entity class is as follows
#Entity
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#Table(name = "ESearch_results", schema = "public")
public class ESearchResponse {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "primary_key", nullable = false)
int primary_key;
#Column(name = "artifact_id")
int artifact_id;
#Column(name = "session_id")
int session_id;
#Column(name = "user_id")
String user_id;
}
my controller is as follows
#RestController
public class EsearchTestController {
#Autowired
private EsearchResponseService EsearchResponseService;
#CrossOrigin
#PostMapping("/justATest")
public String testController(#RequestBody List<ESearchResponse> ESearchResponseTypo){
String result = "this test passed";
// System.out.print("\u001B[35m"+EsearchResponseService.saveESearchResult(ESearchResponseTypo));
System.out.print("\u001B[35m"+ESearchResponseTypo.size());
EsearchResponseService.saveESearchResult(ESearchResponseTypo);
return result;
}
}
I have tried many things and I believe my root issue here is the way I am passing the JSON array in the request body.
Try add a #RequestMapping("/some_path") to your controller.
I used to getting the same problem, and it was the lack of #RequestMapping annotation in my case.
And be careful of your port settings in properties file:
server.port=8000

Include/exclude Attributes in json response from application.yml

I am using JHipster(spring boot) to generate my project. I would like to hide/show fields in JSON from application.yml. for exemple:
I have the following class
#Entity
#Table(name = "port")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Port implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
#Column(name = "id")
private Long id;
#Column(name = "city")
private String city;
#Column(name = "description")
private String description;
//getters & setters
}
My GET method return a response like:
{
"id": 1,
"city": "boston",
"description": "test test"
}
I would like to be able to include/exclude some fields from application.yml (since i don't have application.properties) otherwise to have something like:
//application.yml
include: ['city']
exclude: ['description']
in this exemple my json should look like:
{
"id": 1,
"city": "boston",
}
for exemple if I have 40 fields and I need to hide 10 and show 30 I just want to put the 10 I want to hide in exclude in application.yml without go everytime to change the code. I guess #jsonignore hide fields but I don't know how to do it from application.yml
Sorry for not explaining well. I hope it's clear.
Thank you in advance for any suggestion or solution to do something similar
Spring boot by default uses Jackson JSON library to serialize your classes to Json. In that library there is an annotation #JsonIgnore which is used precisely for the purpose to tell Json engine to egnore a particular property from serialization/de-serialization. So, lets say in your entity Port you would want to exclude property city from showing. All you have to do is to annotate that property (or its getter method) with #JsonIgnore annotation:
#Column(name = "city")
#JsonIgnore
private String city;
You can try to create a hashmap in your controller to manage your HTTP response.
Map<String, Object> map = new HashMap<>();
map.put("id", Port.getId());
map.put("city", Port.getCity());
return map;
Basically you don't expose your Port entity in your REST controller, you expose a DTO (Data Transfer Object) that you value from your entity in service layer using a simple class (e.g PortMapper). PortDTO could also be a Map as suggested in other answer.
Your service layer can then use a configuration object (e.g. PortMapperConfiguration) that is valued from application.yml and used by PortMapper to conditionally call PortDTO setters from Port getters.
#ConfigurationProperties(prefix = "mapper", ignoreUnknownFields = false)
public class PortMapperConfiguration {
private List<String> include;
private List<String> exclude;
// getters/setters
}
#Service
public class PortMapper {
private PortMapperConfiguration configuration;
public PortMapper(PortMapperConfiguration configuration) {
this.configuration = configuration;
}
public PortDTO toDto(Port port) {
PortDTO dto = new PortDTO();
// Fill DTO based on configuration
return dto;
}
}

How can I exclude properties when serializing an object to JSON with jackson?

I have a class implementing Serializable, which is mapped to a database table. It looks like this:
#Entity
#Table(name = "users")
public class Users implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
public Long id;
#Column(name = "name", nullable = false)
public String name;
#Column(name = "email", nullable = false)
public String email;
#Column(name = "status", nullable = false)
public String status;
}
For the most part, I want all these properties to be included in the JSON. However, there is a specific case where I want to exclude status, but I can't figure out a good way of doing this with Jackson.
My controller looks something like this:
public class UserController {
private final ObjectMapper mapper;
#Inject
public UserController(ObjectMapper mapper) {
this.mapper = mapper;
}
public CompletionStage<JsonNode> getUserList() {
// Get list of Users and return the JSON; with all User properties included
}
public CompletionStage<JsonNode> getUser(Long userId) {
// Get a single user from JPA, in a promise
return userDatabase.get(userId).thenApply(user -> { // user is type User
// Here, I don't want to include "status" in the JSON.
return mapper.valueToTree(user);
});
}
}
So when I do mapper.valueToTree(user), of course, it includes all properties of User, but I want to exclude status in this specific route/function while keeping it included in all other places its serialized.
I know I can use #JsonIgnore to ignore it always, but can I do this just sometimes?
Some solutions I thought of are:
filter through the properties and get rid of status
Copy user over to an ObjectNode and manually remove status
Neither of these seems ideal though, I feel like there has to be a cleaner approach with Jackson.

Deserialize JSON to polymorphic types Spring boot

I am working on an e-policy project where i need to save different types of policies. For simplicity i am considering only two types "LifeInsurance" and "AutoInsurance". What i want to achieve is if the JSON request to create policy contains "type":"AUTO_INSURANCE" then the request should be mapped to AutoInsurance.class likewise for LifeInsurance but currently in spring boot app the request is getting mapped to parent class Policy eliminating the specific request fields for auto/Life insurance. The domain model i have created is as below.
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
#NoArgsConstructor
#Getter
#Setter
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include =
JsonTypeInfo.As.PROPERTY, property = "type")
#JsonSubTypes({ #Type(value = AutoInsurance.class, name = "AUTO_INSURANCE"),
#Type(value = LifeInsurance.class) })
public class Policy {
#Id
#GeneratedValue
private Long id;
private String policyNumber;
#Enumerated(EnumType.STRING)
private PolicyType policyType;
private String name;
}
My AutoInsurance class is below.
#Entity
#NoArgsConstructor
#Getter
#Setter
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
#JsonTypeName(value = "AUTO_INSURANCE")
public class AutoInsurance extends Policy {
#Id
#GeneratedValue
private Long id;
private String vehicleNumber;
private String model;
private String vehicleType;
private String vehicleName;
}
Below is LifeInsurance type child class
#Entity
#NoArgsConstructor
#Getter
#Setter
#JsonTypeName(value = "LIFE_INSURANCE")
public class LifeInsurance extends Policy {
#OneToMany(mappedBy = "policy")
private List<Dependents> dependents;
private String medicalIssues;
private String medication;
private String treatments;
}
To save the policy details, I am sending JSON request from UI with a "type" property indicating the type of insurance in the request.
When i run the below test method, JSON request gets mapped to the correct child class as required.
public static void main(String[] args) throws JsonParseException, JsonMappingException, IOException {
ObjectMapper map = new ObjectMapper();
String s = "{\"id\": 1,\"policyNumber\": \"Aut-123\",\"type\": \"AUTO_INSURANCE\",\"policyType\": \"AUTO_INSURANCE\",\"name\": null,\"address\": null,\"contact\": null,\"agentNumber\": null,\"agentName\": null,\"issuedOn\": null,\"startDate\": null,\"endDate\": null,\"vehicleNumber\": \"HR\",\"model\": null,\"vehicleType\": \"SUV\",\"vehicleName\": null}";
Policy p = map.readValue(s, Policy.class);
System.out.println(p.getClass());
//SpringApplication.run(EPolicyApplication.class, args);
}
But when i run the same in Spring boot in a RESTController postmapping, I am getting a PArent class object instead of the child class object.
#RestController
#RequestMapping("/policy")
public class PolicyController {
#PostMapping
public void savePolicy(Policy policy) {
System.out.println(policy.getClass());
}
}
I can get the JSON as string, autowire objectmapper and parse manually but i want to understand if its a known issue and if anyone else has faced the same with Spring boot. I have searched for solutions on this but i got was solution to deserializing to polymorphic classes but nothing related to issue with Spring boot.
In your method you haven't annotated the Policy method argument with #RequestBody. Which leads to Spring creating just an instance of Policy instead of using Jackson to convert the request body.
#PostMapping
public void savePolicy(#RequestBody Policy policy) {
System.out.println(policy.getClass());
}
Adding the #RequestBody will make that Spring uses Jackson to deserialize the request body and with that your annotations/configuration will be effective.

Jackson Custom Deserializer Not Working In Spring Boot

I created a custom deserializer for my entities but it keeps on throwing exception:
I have two classes: AppUser and AppUserAvatar
AppUser.java
#Entity
#Table(name = "user")
public class AppUser implements Serializable {
#Transient
private static final long serialVersionUID = -3536455219051825651L;
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
#Column(name = "password", nullable = false, length = 256)
private String password;
#JsonIgnore
#Column(name = "is_active", nullable = false)
private boolean active;
#JsonIgnore
#OneToMany(mappedBy = "appUser", targetEntity = AppUserAvatar.class, fetch = FetchType.LAZY)
private List<AppUserAvatar> appUserAvatars;
//// Getters and Setters and toString() ////
}
AppUserAvatar.java
#Entity
#Table(name = "user_avatar")
public class AppUserAvatar extends BaseEntityD implements Serializable {
#Transient
private static final long serialVersionUID = 8992425872747011681L;
#Column(name = "avatar", nullable = false)
#Digits(integer = 20, fraction = 0)
#NotEmpty
private Long avatar;
#JsonDeserialize(using = AppUserDeserializer.class)
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id", nullable = false)
private AppUser appUser;
//// Getters and Setters and toString() ////
}
AppUserDeserializer.java
package com.nk.accountservice.deserializer;
import com.edoctar.accountservice.config.exception.InputNotFoundException;
import com.edoctar.accountservice.domain.candidate.AppUser;
import com.edoctar.accountservice.service.candidate.AppUserService;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import org.springframework.beans.factory.annotation.Autowired;
import java.io.IOException;
import java.io.Serializable;
public class AppUserDeserializer extends JsonDeserializer implements Serializable {
private static final long serialVersionUID = -9012464195937554378L;
private AppUserService appUserService;
#Autowired
public void setAppUserService(AppUserService appUserService) {
this.appUserService = appUserService;
}
#Override
public Object deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
JsonNode node = jsonParser.getCodec().readTree(jsonParser);
Long userId = node.asLong();
System.out.println(node);
System.out.println(node.asLong());
AppUser appUser = appUserService.findById(userId);
System.out.println("appuser: " + appUser);
if (appUser == null) try {
throw new InputNotFoundException("User not found!");
} catch (InputNotFoundException e) {
e.printStackTrace();
return null;
}
return appUser;
}
}
Sample xhr boy is:
{
"appUser": 1,
"avatar": 1
}
An exception is thrown each time I submit the request.
Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: (was java.lang.NullPointerException); nested exception is com.fasterxml.jackson.databind.JsonMappingException: (was java.lang.NullPointerException) (through reference chain: com.edoctar.accountservice.domain.candidate.AppUserAvatar["appUser"])]
I discovered that the appUserService.findById() method is not being called. I am really confused. I don't know where I went wrong. Will be greatful for any solution. Thanks.
Updated answer:
You can't use autowired properties because you are not in the Spring context. You are passing the class AppUserDeserializer as a reference in the annotation
#JsonDeserialize(using = AppUserDeserializer.class)
In this situation is the FasterJackson library that creates the instance of AppUserDeserializer, so the Autowired annotation is not taken in consideration.
You can solve your problem with a little trick. Add a static reference to the instance created by spring in the AppUserService:
#Service
public AppUserService {
public static AppUserService instance;
public AppUserService() {
// Modify the constructor setting a static variable holding a
// reference to the instance created by spring
AppUserService.instance = this;
}
...
}
Use that reference in the AppUserDeserializer:
public class AppUserDeserializer extends JsonDeserializer implements Serializable {
private AppUserService appUserService;
public AppUserDeserializer() {
// Set appUserService to the instance created by spring
this.appUserService = AppUserService.instance;
}
...
}
Original answer: To have a correct initialization of an Autowired property you have to annotate your class AppUserDeserializer, otherwise appUserService is null if you don't explicitly init it using the set method.
Try to annotate AppUserDeserializer with #Component:
#Component // Add this annotation
public class AppUserDeserializer extends JsonDeserializer implements Serializable {
...
}
You could go on and try to inject correctly the AppUserService but according to me this is not the cleanest solution. Generally I dislike the idea to use #Entity as communication models or view models. In this way you are coupling the entity to the producer/consumer of the view model. You are basically short-circuiting the model part.
What I would do is to map the contents of the json to a different class in deserialization phase, and use this one later on to construct the corresponding entity.
Try changing this line of code:
private boolean active;
to
private Boolean active;
boolean primitive can't handle nulls and may result in NPE.

Categories