Why #RequestBody works without setters? - java

I am writing a controller with the annotation #RequestBody in order to map to a Java object. The method that uses the annotation is:
#PostMapping("/users")
public ResponseEntity<Object> createUserForProject(#Valid #RequestBody User user) {
log.info("Creating a user " + user.getEmail());
}
This is the User class:
#Getter
#AllArgsConstructor
#Slf4j
#EqualsAndHashCode
#ToString
public class User {
#NotEmpty
#Email
private String email;
#NotEmpty
private String firstName;
#NotEmpty
private String lastName;
#JsonIgnore
private Optional<LocalDate> lastLogonDate = Optional.empty();
#JsonIgnore
private Optional<LocalDate> lastModificationDate = Optional.empty();
#JsonIgnore
private Optional<LocalDate> creationDate = Optional.empty();
#JsonIgnore
private Optional<LocalDate> mfaWarningDate = Optional.empty();
#JsonIgnore
private Optional<LocalDate> auditStartNotificationDate = Optional.empty();
#JsonIgnore
private boolean enabled = true;
public User() {
log.info("HI");
}
(More code without explicit setters)
So when I make a POST call with the body
{
"email":"test#test.com",
"firstName":"testName",
"lastName":"testLastName"
}
Outputs HI and the log with the Creating a user test#test.com message, so the object is created. My point here is... why does this really work? The HttpMessageConverter is calling the no-args constructor and there are no setters to call after create the object with the constructor. How do the object attributes get their values without any setter? What am I missing here?

Spring boot uses Jackson for Object <-> JSON conversion, and Jackson does not need setters, it sets fields via reflection.
Here is a relevant question about Jackson and why it doesn't need setters
How does jackson set private properties without setters?

Related

#Valid annotation is not working and giving NotReadablePropertyException

I have request class for a patch API in the below format
#Schema
#Data
#EqualsAndHashCode(callSuper = true)
#AllArgsConstructor
#NoArgsConstructor
#Builder
public class PricingUsageTemplatePatchInput extends BaseRequest {
#Schema(description = "From node of the Template")
private JsonNullable<#Valid VertexNode> from;
#Schema(description = "To node of the Template")
private JsonNullable<#Valid VertexNode> to;
#NotNull
#Schema(description = "Current version of the template for which update is being made.")
private Long version;
}
VertexNode is as below
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
public class VertexNode {
#NotNull
#Valid
private Selectors selectors;
#NotNull
#Valid
private Cardinality cardinality;
}
And in the controller layer is as below
#PatchMapping("/{key}")
#Operation(description = "API to update the template")
public PricingUsageTemplateResponse update(#PathVariable #IsUUID final String key,
#Valid #RequestBody #NotNull #Parameter(description = "PricingUsageTemplatePatchInput", required = true)
PricingUsageTemplatePatchInput pricingUsagePatchInput) {
var request = PricingUsageTemplateUpdateRequest.builder()
.key(key)
.pricingUsageTemplatePatchInput(pricingUsageTemplatePatchInput)
.build();
return (PricingUsageTemplateResponse) actionRegistry.get(request).invoke(request);
}
When I am sending selector as null from the postman for the above api , the valid annotation is not able to send valid not null validation error instead I am getting 5xx error with below reason
org.springframework.beans.NotReadablePropertyException: Invalid property 'to.selectors' of bean class [domain.pricing_usage.dto.request.PricingUsageTemplatePatchInput]: Bean property 'to.selectors' is not readable or has an invalid getter method: Does the return type of the getter match the parameter type of the setter?
Can anyone help why #Valid is not working as expected

Is which the best request parameter mapping strategy in Spring Framework?

I am Java web developer, usually develop Spring MVC.
I have been using #RequestMapping or #RequestParam for mapping to hashMap at Controller.
It is a terrible way. I should always cast type when using value.
But nowadays I try to use #ModelAttribute to write clean code at Controller.
However, there are some problem.
case 1) make DTO for each EndPoint.
We can make DTO for each EndPoint, but DTO will have many duplicated property.
#Getter
#Setter
#ToString
class GetUserInfoDTO {
private String id;
private String name;
}
#Getter
#Setter
#ToString
class PostUserInfoDTO {
private String name;
private Integer age;
private String address;
private String gender;
private String email;
private Date joinDate;
}
in controller,
#GetMapping("/user")
public ResultDTO getUserInfo (#ModelAttribute GetUserInfoDTO){
...
return ResultDTO;
}
#PostMapping("/user")
public ResultDTO postUserInfo (#ModelAttribute PostUserInfoDTO){
...
return ResultDTO;
}
In this case, we can apply independent validation strategy for each End-Point.
for example..
#Getter
#Setter
#ToString
class GetUserInfoDTO {
#NotNull
private String id;
private String name;
}
#Getter
#Setter
#ToString
class PostUserInfoDTO {
#NotNull
private String name;
#NotNull
private Integer age;
#NotEmpty
private String address;
private String gender;
private String email;
private Date joinDate;
}
like this.
But so many model classes made, and so many duplicated property exists.
case 2. make common DTO for each Controller.
We can make DTO for each Controller, and reuse them.
#Getter
#Setter
#ToString
class UserInfoDTO {
private String id;
private String name;
private Integer age;
private String address;
private String gender;
private String email;
private Date joinDate;
}
#GetMapping("/user")
public ResultDTO getUserInfo (#ModelAttribute UserInfoDTO){
//I want only id, name
...
return ResultDTO;
}
#PostMapping("/user")
public ResultDTO postUserInfo (#ModelAttribute UserInfoDTO){
...
return ResultDTO;
}
But In this case, we can only pass specific properties.
If someone send other parameter than id and name, we can't notice. ( 400 error not occur )
Code assistance can't recommend us specific properties that use at single end-point.
I don't like these cases.
In first case, I should make so many models and It's management will be so hard.
Second case, unnecessary properties exists and it hard to validate for each end-point.
Which way is the best?
Or Can you recommend another way for mapping request parameter to model object?

How to access the String json payload and mapping it to an Object in Spring rest controller?

I'm building a rest API using Spring Boot rest services.
I have a Java class:
class Person{
int id;
#notNull
String name;
#notNull
String password;
}
And I want to make an API to create a Person object. I will recieve a POST request with json body like:
{
"name":"Ahmad",
"password":"myPass",
"shouldSendEmail":1
}
As you can see there are an extra field "shouldSendEmail" that I have to use it to know if should I send an email or not after I create the Person Object.
I am using the following API:
#RequestMapping(value = "/AddPerson", method = RequestMethod.POST)
public String savePerson(
#Valid #RequestBody Person person) {
personRepository.insert(person);
// Here I want to know if I should send an email or Not
return "success";
}
Is there a method to access the value of "shouldSendEmail" while I using the autoMapping in this way?
There's many options for you solve. Since you don't want to persist the shouldSendEmail flag and it's ok to add into you domain class, you can use the #Transient annotation to tell JPA to skip the persistence.
#Entity
public class Person {
#Id
private Integer id;
#NotNull
private String name;
#NotNull
private String password;
#Transient
private Boolean shouldSendEmail;
}
If you want more flexible entity personalizations, I recommend using DTO`s.
MapStruct is a good library to handle DTO`s
You will need an intermediary DTO, or you will otherwise have to modify person to include a field for shouldSendEmail. If that is not possible, the only other alternative is to use JsonNode and manually select the properties from the tree.
For example,
#Getter
public class PersonDTO {
private final String name;
private final String password;
private final Integer shouldSendEmail;
#JsonCreator
public PersonDTO(
#JsonProperty("name") final String name,
#JsonProperty("password") final String password,
#JsonProperty("shouldSendEmail") final Integer shouldSendEmail
) {
this.name = name;
this.password = password;
this.shouldSendEmail = shouldSendEmail;
}
}
You can use #RequestBody and #RequestParam together as following
.../addPerson?sendEmail=true
So send the “sendEmail” value as request param and person as request body
Spring MVC - Why not able to use #RequestBody and #RequestParam together
You have mutli solutions
1 - You can put #Column(insertable=false, updatable=false) above this property
2 - send it as request param #RequestParam
#RequestMapping(value = "/AddPerson", method = RequestMethod.POST)
public String savePerson(
#Valid #RequestBody Person person, #RequestParam boolean sendMail) {}
3- use DTO lets say PersonModel and map it to Person before save

#JsonIgnore with #Getter Annotation

Can I use #JsonIgnore with #Getter annotation from lombok without explicitly define the getter, because I have to use this JsonIgnore while serializing the object but while deserializing, the JsonIgnore annotation must be ignored so the field in my object must not be null?
#Getter
#Setter
public class User {
private userName;
#JsonIgnore
private password;
}
I know, just by define the JsonIgnore on the getter of password I can prevent my password to be serialized but for that, I have to explicitly define the getter thing that I don't want.
Any idea please, Any help will be appreciated.
To put the #JsonIgnore to the generated getter method, you can use onMethod = #__( #JsonIgnore ). This will generate the getter with the specific annotation. For more details check
http://projectlombok.org/features/GetterSetter.html
#Getter
#Setter
public class User {
private userName;
#Getter(onMethod = #__( #JsonIgnore ))
#Setter
private password;
}
Recently i had the same issue using jackson-annotation 2.9.0 and lombok 1.18.2
This is what worked for me:
#Getter
#Setter
public class User {
#JsonIgnore
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private String password;
So basically adding the annotation #JsonProperty(access = JsonProperty.Access.WRITE_ONLY) means that the property may only be written for deserialization (using setter) but will not be read on serialization (using getter)
This could be quite obvious but I lost a lot of time not thinking this solution before:
#Getter
#Setter
public class User {
private userName;
#Setter
private password;
#JsonIgnore
public getPassword() { return password; }
}
As Sebastian said #__( #JsonIgnore ) can resolve this issue but sometimes the use of the onX Lombok feature (#__()) can have side-effects for example breaking the javadoc generation.
I recently had the same issue.
There are several ways to solve it:
Create file lombok.config in the root folder of your project with content:
// says that it's primary config (lombok will not scan other folders then)
config.stopBubbling = true
// forces to copy #JsonIgnore annotation on generated constructors / getters / setters
lombok.copyableAnnotations += com.fasterxml.jackson.annotation.JsonIgnore
...
and in your class you can use this annotation as usual, on the field level:
#JsonIgnore
private String name;
Note: if you use lombok #RequiredArgsConstructor or #AllArgsConstructor, then you should remove all usages of #JsonIgnore with #JsonIgnoreProperties (as described in solution #4, or you may still choose solution #2 or #3). This is required because #JsonIgnore annotation is not applicable for constructor arguments.
Define Getters / Setters manually + add #JsonIgnore annotation on them:
#JsonIgnore
public String getName() { return name; }
#JsonIgnore
public void setName(String name) { this.name = name; }
Use #JsonProperty (it's either read-only or write-only, but no both):
#JsonProperty(access = JsonProperty.Access.READ_ONLY) // will be ignored during serialization
private String name;
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY) // will be ignored during deserialization
private String name;
Use #JsonIgnoreProperties({ "fieldName1", "fieldName2", "..."})
I personally use solution #1 globally and solution #4 for exceptions when class also has annotations #AllArgsConstructor or #RequiredArgsConstructor.
With JDK version 8 use below:
// #Getter(onMethod=#__({#Id, #Column(name="unique-id")})) //JDK7
// #Setter(onParam=#__(#Max(10000))) //JDK7
#Getter(onMethod_={#Id, #Column(name="unique-id")}) //JDK8
#Setter(onParam_=#Max(10000)) //JDK8
Source : https://projectlombok.org/features/experimental/onX

Only using #JsonIgnore during serialization, but not deserialization

I have a user object that is sent to and from the server. When I send out the user object, I don't want to send the hashed password to the client. So, I added #JsonIgnore on the password property, but this also blocks it from being deserialized into the password that makes it hard to sign up users when they don't have a password.
How can I only get #JsonIgnore to apply to serialization and not deserialization? I'm using Spring JSONView, so I don't have a ton of control over the ObjectMapper.
Things I've tried:
Add #JsonIgnore to the property
Add #JsonIgnore on the getter method only
Exactly how to do this depends on the version of Jackson that you're using. This changed around version 1.9, before that, you could do this by adding #JsonIgnore to the getter.
Which you've tried:
Add #JsonIgnore on the getter method only
Do this, and also add a specific #JsonProperty annotation for your JSON "password" field name to the setter method for the password on your object.
More recent versions of Jackson have added READ_ONLY and WRITE_ONLY annotation arguments for JsonProperty. So you could also do something like:
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private String password;
Docs can be found here.
In order to accomplish this, all that we need is two annotations:
#JsonIgnore
#JsonProperty
Use #JsonIgnore on the class member and its getter, and #JsonProperty on its setter. A sample illustration would help to do this:
class User {
// More fields here
#JsonIgnore
private String password;
#JsonIgnore
public String getPassword() {
return password;
}
#JsonProperty
public void setPassword(final String password) {
this.password = password;
}
}
Since version 2.6: a more intuitive way is to use the com.fasterxml.jackson.annotation.JsonProperty annotation on the field:
#JsonProperty(access = Access.WRITE_ONLY)
private String myField;
Even if a getter exists, the field value is excluded from serialization.
JavaDoc says:
/**
* Access setting that means that the property may only be written (set)
* for deserialization,
* but will not be read (get) on serialization, that is, the value of the property
* is not included in serialization.
*/
WRITE_ONLY
In case you need it the other way around, just use Access.READ_ONLY.
In my case, I have Jackson automatically (de)serializing objects that I return from a Spring MVC controller (I am using #RestController with Spring 4.1.6). I had to use com.fasterxml.jackson.annotation.JsonIgnore instead of org.codehaus.jackson.annotate.JsonIgnore, as otherwise, it simply did nothing.
Another easy way to handle this is to use the argument allowSetters=truein the annotation. This will allow the password to be deserialized into your dto but it will not serialize it into a response body that uses contains object.
example:
#JsonIgnoreProperties(allowSetters = true, value = {"bar"})
class Pojo{
String foo;
String bar;
}
Both foo and bar are populated in the object, but only foo is written into a response body.
"user": {
"firstName": "Musa",
"lastName": "Aliyev",
"email": "klaudi2012#gmail.com",
"passwordIn": "98989898", (or encoded version in front if we not using https)
"country": "Azeribaijan",
"phone": "+994707702747"
}
#CrossOrigin(methods=RequestMethod.POST)
#RequestMapping("/public/register")
public #ResponseBody MsgKit registerNewUsert(#RequestBody User u){
root.registerUser(u);
return new MsgKit("registered");
}
#Service
#Transactional
public class RootBsn {
#Autowired UserRepository userRepo;
public void registerUser(User u) throws Exception{
u.setPassword(u.getPasswordIn());
//Generate some salt and setPassword (encoded - salt+password)
User u=userRepo.save(u);
System.out.println("Registration information saved");
}
}
#Entity
#JsonIgnoreProperties({"recordDate","modificationDate","status","createdBy","modifiedBy","salt","password"})
public class User implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String country;
#Column(name="CREATED_BY")
private String createdBy;
private String email;
#Column(name="FIRST_NAME")
private String firstName;
#Column(name="LAST_LOGIN_DATE")
private Timestamp lastLoginDate;
#Column(name="LAST_NAME")
private String lastName;
#Column(name="MODIFICATION_DATE")
private Timestamp modificationDate;
#Column(name="MODIFIED_BY")
private String modifiedBy;
private String password;
#Transient
private String passwordIn;
private String phone;
#Column(name="RECORD_DATE")
private Timestamp recordDate;
private String salt;
private String status;
#Column(name="USER_STATUS")
private String userStatus;
public User() {
}
// getters and setters
}
You can use #JsonIgnoreProperties at class level and put variables you want to igonre in json in "value" parameter.Worked for me fine.
#JsonIgnoreProperties(value = { "myVariable1","myVariable2" })
public class MyClass {
private int myVariable1;,
private int myVariable2;
}
You can also do like:
#JsonIgnore
#JsonProperty(access = Access.WRITE_ONLY)
private String password;
It's worked for me
I was looking for something similar. I still wanted my property serialized but wanted to alter the value using a different getter. In the below example, I'm deserializing the real password but serializing to a masked password. Here's how to do it:
public class User() {
private static final String PASSWORD_MASK = "*********";
#JsonIgnore
private String password;
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
public String setPassword(String password) {
if (!password.equals(PASSWORD_MASK) {
this.password = password;
}
}
public String getPassword() {
return password;
}
#JsonProperty("password")
public String getPasswordMasked() {
return PASSWORD_MASK;
}
}
The ideal solution would be to use DTO (data transfer object)

Categories