This works for me
@Data
public class ProductRequest {
private Optional<String> name;
private Optional<String> unitSale;
}
The most important thing here is to use Optional<> in the DTO class, because Jackson when deserializing distinguishes between an explicit null in the request body and a null due to the omission of the parameter in the request.
Example:
If we make the following PUT request with parameters
http put localhost:8080/products/200 name=banana unit_sale=KG
We obtain the following DTO
ProductRequest(name=Optional[banana], unitSale=Optional[KG])
And when we send an explicit null value:
http put :8080/products/200 name=banana unit_sale:=null
ProductRequest(name=Optional[banana], unitSale=Optional.empty)
And when we send some parameters omitting the others
http put :8080/products/200 name=banana
ProductRequest(name=Optional[banana], unitSale=null)
And that's the key, distinguishing between Optional.empty and null in our business logic to determine what value to set in our entity.
@Transactional
public void update(Long productId, @Valid ProductRequest productRequest) {
ProductEntity productEntity = productRepository.findByIdOptional(productId)
.orElseThrow(() -> new EntityNotFoundException("Product ID not found"));
if (productRequest.getName() != null) {
productEntity.name = unwrapOptional(productRequest.getName());
}
if (productRequest.getUnitSale() != null) {
productEntity.unitSale = unwrapOptional(productRequest.getUnitSale());
}
}
public <T> T unwrapOptional(final Optional<T> o) {
return o.isPresent() ? o.get() : null;
}