Java not null dto

How to distinguish between null and not provided values for partial updates in Spring Rest Controller

I’m trying to distinguish between null values and not provided values when partially updating an entity with PUT request method in Spring Rest Controller. Consider the following entity, as an example:

@Entity private class Person < @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; /* let's assume the following attributes may be null */ private String firstName; private String lastName; /* getters and setters . */ >

@Repository public interface PersonRepository extends CrudRepository

@RestController @RequestMapping("/api/people") public class PersonController < @Autowired private PersonRepository people; @Transactional @RequestMapping(path = "/", method = RequestMethod.PUT) public ResponseEntity update( @PathVariable String personId, @RequestBody PersonDTO dto) < // get the entity by ID Person p = people.findOne(personId); // we assume it exists // update ONLY entity attributes that have been defined if(/* dto.getFirstName is defined */) p.setFirstName = dto.getFirstName; if(/* dto.getLastName is defined */) p.setLastName = dto.getLastName; return ResponseEntity.ok(p); >> 

Expected behaviour: update firstName= «John» (leave lastName unchanged). Request with null property

Expected behaviour: update firstName=»John» and set lastName=null . I cannot distinguish between these two cases, since lastName in the DTO is always set to null by Jackson. Note: I know that REST best practices (RFC 6902) recommend using PATCH instead of PUT for partial updates, but in my particular scenario I need to use PUT.

8 Answers 8

Another option is to use java.util.Optional.

import com.fasterxml.jackson.annotation.JsonInclude; import java.util.Optional; @JsonInclude(JsonInclude.Include.NON_NULL) private class PersonDTO < private OptionalfirstName; private Optional lastName; /* getters and setters . */ > 

If firstName is not set, the value is null, and would be ignored by the @JsonInclude annotation. Otherwise, if implicitly set in the request object, firstName would not be null, but firstName.get() would be. I found this browsing the solution @laffuste linked to a little lower down in a different comment (garretwilson’s initial comment saying it didn’t work turns out to work).

You can also map the DTO to the Entity with Jackson’s ObjectMapper, and it will ignore properties that were not passed in the request object:

import com.fasterxml.jackson.databind.ObjectMapper; class PersonController < // . @Autowired ObjectMapper objectMapper @Transactional @RequestMapping(path = "/", method = RequestMethod.PUT) public ResponseEntity update( @PathVariable String personId, @RequestBody PersonDTO dto ) < Person p = people.findOne(personId); objectMapper.updateValue(p, dto); personRepository.save(p); // return . >> 

Validating a DTO using java.util.Optional is a little different as well. It’s documented here, but took me a while to find:

// . import javax.validation.constraints.NotNull; import javax.validation.constraints.NotBlank; import javax.validation.constraints.Pattern; // . private class PersonDTO < private OptionalfirstName; private Optional lastName; /* getters and setters . */ > 

In this case, firstName may not be set at all, but if set, may not be set to null if PersonDTO is validated.

//. import javax.validation.Valid; //. public ResponseEntity update( @PathVariable String personId, @RequestBody @Valid PersonDTO dto ) < // . >

Also might be worth mentioning the use of Optional seems to be highly debated, and as of writing Lombok’s maintainer(s) won’t support it (see this question for example). This means using lombok.Data/lombok.Setter on a class with Optional fields with constraints doesn’t work (it attempts to create setters with the constraints intact), so using @Setter/@Data causes an exception to be thrown as both the setter and the member variable have constraints set. It also seems better form to write the Setter without an Optional parameter, for example:

//. import lombok.Getter; //. @Getter private class PersonDTO < private OptionalfirstName; private Optional lastName; public void setFirstName(String firstName) < this.firstName = Optional.ofNullable(firstName); >// etc. > 

is it possible to cascade @Valid ? (cause it doesn’t seem to work) I mean If I head Controller -> @Valid PersonDTO —inside—> @Valid private Optional> animals; ->

Читайте также:  Python get command line param

There is a better option, that does not involve changing your DTO’s or to customize your setters.

It involves letting Jackson merge data with an existing data object, as follows:

MyData existingData = . ObjectReader readerForUpdating = objectMapper.readerForUpdating(existingData); MyData mergedData = readerForUpdating.readValue(newData); 

Any fields not present in newData will not overwrite data in existingData , but if a field is present it will be overwritten, even if it contains null .

 ObjectMapper objectMapper = new ObjectMapper(); MyDTO dto = new MyDTO(); dto.setText("text"); dto.setAddress("address"); dto.setCity("city"); String json = ""; ObjectReader readerForUpdating = objectMapper.readerForUpdating(dto); MyDTO merged = readerForUpdating.readValue(json); 

Note that text and city were patched ( city is now null ) and that address was left alone.

In a Spring Rest Controller you will need to get the original JSON data instead of having Spring deserialize it in order to do this. So change your endpoint like this:

@Autowired ObjectMapper objectMapper; @RequestMapping(path = "/", method = RequestMethod.PATCH) public ResponseEntity update( @PathVariable String personId, @RequestBody JsonNode jsonNode)

why did you use a plain string as json? I think you break the example here. You should answer with an entity decoded already.

@Sebastian I really don’t understand what you are asking here — to demo how it works, I used a string, what is the issue? See the last example with the Spring controller, there is no json string there.

Decent answer. The only issue here is, you have to manually validate the mergedData DTO and then you might get unwanted errors if validation fails because of wrong existing data.

@Michał Jabłoński In any case, I think you would need to revalidate the merged entity after applying a partial update, as there may be cross-field validations of modified AND unmodified fields.

class PersonDTO < private String firstName; private boolean isFirstNameDirty; public void setFirstName(String firstName)< this.firstName = firstName; this.isFirstNameDirty = true; >public String getFirstName() < return firstName; >public boolean hasFirstName() < return isFirstNameDirty; >> 

This solution works but I would argue this is a failing of Jackson and results in a huge amount of code bloat. good reason to not use it. Looks like GSON is a good alternative: github.com/google/gson/blob/master/…

Actually,if ignore the validation,you can solve your problem like this.

 public class BusDto < private MapchangedAttrs = new HashMap<>(); /* getter and setter */ > 
  • First, write a super class for your dto,like BusDto.
  • Second, change your dto to extend the super class, and change the dto’s set method,to put the attribute name and value to the changedAttrs(beacause the spring would invoke the set when the attribute has value no matter null or not null).
  • Third,traversal the map.
Читайте также:  Шрифты для css подключить

I have tried to solve the same problem. I found it quite easy to use JsonNode as the DTOs. This way you only get what is submitted.

You will need to write a MergeService yourself that does the actual work, similar to the BeanWrapper. I haven’t found an existing framework that can do exactly what is needed. (If you use only Json requests you might be able to use Jacksons readForUpdate method.)

We actually use another node type as we need the same functionality from «standard form submits» and other service calls. Additionally the modifications should be applied within a transaction inside something called EntityService .

This MergeService will unfortunately become quite complex, as you will need to handle properties, lists, sets and maps yourself 🙂

The most problematic piece for me was to distinguish between changes within an element of a list/set and modifications or replacements of lists/sets.

And also validation will not be easy as you need to validate some properties against another model (the JPA entities in my case)

EDIT — Some mapping code (pseudo-code):

class SomeController < @RequestMapping(value = < "/" >, method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE) @ResponseBody public void save( @PathVariable("id") final Integer id, @RequestBody final JsonNode modifications) < modifierService.applyModifications(someEntityLoadedById, modifications); >> class ModifierService < public void applyModifications(Object updateObj, JsonNode node) throws Exception < BeanWrapperImpl bw = new BeanWrapperImpl(updateObj); IteratorfieldNames = node.fieldNames(); while (fieldNames.hasNext()) < String fieldName = fieldNames.next(); Object valueToBeUpdated = node.get(fieldName); ClasspropertyType = bw.getPropertyType(fieldName); if (propertyType == null) < if (!ignoreUnkown) < throw new IllegalArgumentException("Unkown field " + fieldName + " on type " + bw.getWrappedClass()); >> else if (Map.class.isAssignableFrom(propertyType)) < handleMap(bw, fieldName, valueToBeUpdated, ModificationType.MODIFY, createdObjects); >else if (Collection.class.isAssignableFrom(propertyType)) < handleCollection(bw, fieldName, valueToBeUpdated, ModificationType.MODIFY, createdObjects); >else < handleObject(bw, fieldName, valueToBeUpdated, propertyType, createdObjects); >> > > 

Источник

How to exclude value null from Put request when mapping to dto

I using RestController update data to db but I have problem. When i update value, if value from my update is null , it allways update data to db is null. I dont’t want it. I want if 1 field with value is null from my request, i don’t want update it. This bellow my code : Controller: RestController

@RequestMapping("/api/products") @Api(value = "ProductControllerApi",produces = MediaType.APPLICATION_JSON_VALUE) public class ProductController < @Autowired private ProductService productService; @PatchMapping("/") public ResponseEntity updateProduct(@RequestBody ProductReqDto productReqDto, @PathVariable String id)
 public class ProductReqDto < private String name; private String type; private String category; private String description; private Double prince; public String getName() < return name; >public void setName(String name) < this.name = name; >public String getType() < return type; >public void setType(String type) < this.type = type; >public String getCategory() < return category; >public void setCategory(String category) < this.category = category; >public String getDescription() < return description; >public void setDescription(String description) < this.description = description; >public Double getPrince() < return prince; >public void setPrince(Double prince) < this.prince = prince; >> 
 public class ProductResDto < private String name; private String type; private String category; private Double prince; public String getName() < return name; >public void setName(String name) < this.name = name; >public String getType() < return type; >public void setType(String type) < this.type = type; >public String getCategory() < return category; >public void setCategory(String category) < this.category = category; >public String getDescription() < return description; >public void setDescription(String description) < this.description = description; >public Double getPrince() < return prince; >public void setPrince(Double prince) < this.prince = prince; >> 
private ProductDto convertToProductDto(ProductReq product)

How to i handle method convertToProductDto only mapping with value not null. Because if , mapping one field : example : product_name = null , it insert to db null. I want if field ProductReq have value, it mapping and keep other different field in database(not set it null if not contain value from ProductReq) . Example:

**ReqProductDto.class** private String name; private String type; private String category; private String description; private Double prince; 
 private String name; private String type; 

I want spring update field name, and field type user input and keep category,description,prince in my database. In my case, if user update two field: name, and field type,spring update it but spring set category,description,prince is null in my database. I don't want it. Please help me, thanks.

Читайте также:  Glob python что это

Источник

To check null condition in all Dto members. If not then set the value to the same attribute of the pojo class

I'm using spring boot for restful webservices, and I've many DTO and Model objects. When doing post request, front end user is sending a DTO type object. Dto has mostly similar members of Model object. I'm checking the null constraint of each member in DTO object and if not then set the value to similar attributes of MODEL object. I've briefly defined my case below,

class UserDto < private String userid; private String username; private String dept; public String getUserid() < return userid; >public void setUserid(String userid) < this.userid = userid; >public String getUsername() < return username; >public void setUsername(String username) < this.username = username; >public String getDept() < return dept; >public void setDept(String dept) < this.dept = dept; >> 
 class User < private String userid; private String username; private String dept; //some constructors . public String getUserid() < return userid; >public void setUserid(String userid) < this.userid = userid; >public String getUsername() < return username; >public void setUsername(String username) < this.username = username; >public String getDept() < return dept; >public void setDept(String dept) < this.dept = dept; >> 
if (userDto.getUserid()!= null) user.setUserid(userDto.getUserid()); if (userDto.getUsername()!= null) user.setUsername(userDto.getUsername()); 

I've already looked at this link What is the best way to know if all the variables in a Class are null? . This only tells that how to find the nullable members in an object, but in my case I want to find the not null member and copy to the another member in an object. ie if userDto.getUsername()!==null then user.setUsername(userDto.getUsername()) . This is all I need.

Источник

Оцените статью