10

In my spring boot service, I'm using https://github.com/java-json-tools/json-patch for handling PATCH requests.

Everything seems to be ok except a way to avoid modifying immutable fields like object id's, creation_time etc. I have found a similar question on Github https://github.com/java-json-tools/json-patch/issues/21 for which I could not find the right example.

This blog seems to give some interesting solutions about validating JSON patch requests with a solution in node.js. Would be good to know if something similar in JAVA is already there.

shoaib1992
  • 380
  • 1
  • 6
  • 23

2 Answers2

9

Under many circumstances you can just patch an intermediate object which only has fields that the user can write to. After that you could quite easily map the intermediate object to your entity, using some object mapper or just manually.

The downside of this is that if you have a requirement that fields must be explicitly nullable, you won’t know if the patch object set a field to null explicitly or if it was never present in the patch.

What you can do too is abuse Optionals for this, e.g.

public class ProjectPatchDTO {

    private Optional<@NotBlank String> name;
    private Optional<String> description;
}

Although Optionals were not intended to be used like this, it's the most straightforward way to implement patch operations while maintaining a typed input. When the optional field is null, it was never passed from the client. When the optional is not present, that means the client has set the value to null.

Sebastiaan van den Broek
  • 5,262
  • 5
  • 35
  • 68
1

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);
    }
Enock Lubowa
  • 569
  • 5
  • 11
  • Your "toString" cannot distinguish between null and not provided values, which is the whole point of patch. See https://stackoverflow.com/questions/38424383/how-to-distinguish-between-null-and-not-provided-values-for-partial-updates-in-s – Flyout91 May 10 '22 at 11:55