Java中的JSON补丁请求验证

bejyjqdl  于 2022-12-15  发布在  Java
关注(0)|答案(3)|浏览(138)

在我的Spring Boot 服务中,我使用https://github.com/java-json-tools/json-patch来处理PATCH请求。
一切似乎都很好,除了一种方法,以避免修改不可变的字段,如对象id的,创建时间等,我发现了一个类似的question在Github https://github.com/java-json-tools/json-patch/issues/21,我找不到正确的例子。
这个blog似乎给予了一些有趣的解决方案,关于使用node.js中的解决方案验证JSON补丁请求。如果知道JAVA中是否已经有类似的解决方案,那就太好了。

368yc8dk

368yc8dk1#

在很多情况下,你可以只修补一个只有用户可以写入的字段的中间对象,然后你可以很容易地使用一些对象Map器或手动地将中间对象Map到你的实体。
这样做的缺点是,如果您要求字段必须显式地为空,那么您将不知道补丁对象是否显式地将字段设置为空,或者它是否从未出现在补丁中。
您也可以为此滥用可选选项,例如:

public class ProjectPatchDTO {

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

尽管Optional不是这样使用的,但它是在维护输入类型的同时实现修补操作的最直接的方法。当可选字段为null时,它从未从客户端传递。当可选字段不存在时,这意味着客户端已将值设置为null。

l0oc07j2

l0oc07j22#

不要直接从客户端接收JsonPatch,而是定义一个DTO来处理验证,然后将DTO示例转换为JsonPatch
假设您要更新示例User.class的用户,则可以定义如下DTO:

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
}

自定义toString方法确保不使用null值预先填充更新请求中不包含的字段。
您的PATCH请求可以如下所示(为了简单起见,我没有考虑例外)

@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);
    }

以下方法返回上面在控制器中更新的已修补用户。

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);
    }
nzk0hqpo

nzk0hqpo3#

另一个解决方案是强制反序列化并验证请求主体
因此,示例DTO可能如下所示:

public class CatDto {
    @NotBlank
    private String name;

    @Min(0)
    @Max(100)
    private int laziness;

    @Max(3)
    private int purringVolume;
}

你的控制器可以是这样的:

@RestController
@RequestMapping("/api/cats")
@io.swagger.v3.oas.annotations.parameters.RequestBody(
        content = @Content(schema = @Schema(implementation = CatDto.class)))
// ^^ this passes your CatDto model to swagger (you must use springdoc to get it to work!)
public class CatController {
    @Autowired
    SmartValidator validator; // we'll use this to validate our request

    @PatchMapping(path = "/{id}", consumes = "application/json")
    public ResponseEntity<String> updateCat(
            @PathVariable String id,
            @RequestBody Map<String, Object> body
            // ^^ no Valid annotation, no declarative DTO binding here!
    ) throws MethodArgumentNotValidException {
        CatDto catDto = new CatDto();
        WebDataBinder binder = new WebDataBinder(catDto);
        BindingResult bindingResult = binder.getBindingResult();

        binder.bind(new MutablePropertyValues(body));
        // ^^ imperatively bind to DTO
        body.forEach((k, v) -> validator.validateValue(CatDto.class, k, v, bindingResult));
        // ^^ imperatively validate user input
        if (bindingResult.hasErrors()) {
            throw new MethodArgumentNotValidException(null, bindingResult);
            // ^^ this can be handled by your regular exception handler
        }
        // Here you can do normal stuff with your cat DTO.
        // Map it to cat model, send to cat service, whatever.
        return ResponseEntity.ok("cat updated");
    }

}

不需要Optional's,没有额外的依赖,你的正常验证就可以了,你的昂首阔步看起来不错。唯一的问题是,你没有在嵌套对象上得到正确的合并补丁,但在许多用例中,这甚至是不需要的。

相关问题