Instead of receiving a JsonPatch
directly from the client, define a DTO to handle the validation and then you will later convert the DTO instance to a JsonPatch
.
Say you want to update a user of instance User.class
, you can define a DTO such as:
public class UserDTO {
@Email(message = "The provided email is invalid")
private String username;
@Size(min = 2, max = 10, message = "firstname should have at least 2 and a maximum of 10 characters")
private String firstName;
@Size(min = 2, max = 10, message = "firstname should have at least 2 and a maximum of 10 characters")
private String lastName;
@Override
public String toString() {
return new Gson().toJson(this);
}
//getters and setters
}
The custom toString
method ensures that fields that are not included in the update request are not prefilled with null
values.
Your PATCH
request can be as follows(For simplicity, I didn't cater for Exceptions)
@PatchMapping("/{id}")
ResponseEntity<Object> updateUser(@RequestBody @Valid UserDTO request,
@PathVariable String id) throws ParseException, IOException, JsonPatchException {
User oldUser = userRepository.findById(id);
String detailsToUpdate = request.toString();
User newUser = applyPatchToUser(detailsToUpdate, oldUser);
userRepository.save(newUser);
return userService.updateUser(request, id);
}
The following method returns the patched User which is updated above in the controller.
private User applyPatchToUser(String detailsToUpdate, User oldUser) throws IOException, JsonPatchException {
ObjectMapper objectMapper = new ObjectMapper();
// Parse the patch to JsonNode
JsonNode patchNode = objectMapper.readTree(detailsToUpdate);
// Create the patch
JsonMergePatch patch = JsonMergePatch.fromJson(patchNode);
// Convert the original object to JsonNode
JsonNode originalObjNode = objectMapper.valueToTree(oldUser);
// Apply the patch
TreeNode patchedObjNode = patch.apply(originalObjNode);
// Convert the patched node to an updated obj
return objectMapper.treeToValue(patchedObjNode, User.class);
}