You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring中使用PATCH传递多类型JSON参数时类型不匹配如何解决?

解决PATCH请求中类型不匹配导致的IllegalArgumentException问题

问题根源

JSON反序列化时,所有数字会默认解析为Integer类型,但你的GradeEto中subjectEntityId是Long类型,直接通过ReflectionUtils赋值时,Integer无法直接转换为Long,触发类型不匹配异常。同理其他类型不匹配场景也会出现该问题。

解决方案

方案1:手动添加类型转换逻辑

在反射赋值前,根据目标字段类型对传入值做转换,适配字段类型:

@Override
public GradeEto partialUpdate(Long id, Map<String, Object> updateInfo) {
    Grade grade = gradeRepository.findById(id).get();
    GradeEto gradeeto = GradeMapper.mapToETO(grade);
    
    updateInfo.forEach((key, value) -> {
        Field field = ReflectionUtils.findField(GradeEto.class, key);
        if (field != null) {
            field.setAccessible(true);
            Class<?> targetType = field.getType();
            Object convertedValue = convertValue(value, targetType);
            ReflectionUtils.setField(field, gradeeto, convertedValue);
        }
    });
    // 后续保存及返回逻辑...
    return gradeeto;
}

// 类型转换工具方法
private Object convertValue(Object value, Class<?> targetType) {
    if (value == null || targetType.isAssignableFrom(value.getClass())) {
        return value;
    }
    // 处理数字类型转换
    if (Number.class.isAssignableFrom(targetType)) {
        Number number = (Number) value;
        if (targetType == Long.class || targetType == long.class) {
            return number.longValue();
        } else if (targetType == Integer.class || targetType == int.class) {
            return number.intValue();
        } else if (targetType == Double.class || targetType == double.class) {
            return number.doubleValue();
        }
    }
    // 可扩展其他类型转换,如String转LocalDateTime等
    return value;
}

方案2:使用专用Patch DTO接收参数

放弃Map<String, Object>,定义包含允许修改字段的DTO,让Spring自动完成类型转换,同时实现编译期类型校验:

// 定义Patch专用DTO
public class GradePatchDto {
    private Integer value;
    private String comment;
    private Long subjectEntityId;

    // 生成getter、setter方法
}

// 修改Controller方法
@PatchMapping("/grades/{id}")
public GradeEto partialUpdate(@PathVariable("id") final Long id, @RequestBody GradePatchDto updateInfo) {
    return gradeService.partialUpdate(id, updateInfo);
}

// 修改Service逻辑
@Override
public GradeEto partialUpdate(Long id, GradePatchDto updateInfo) {
    Grade grade = gradeRepository.findById(id).get();
    GradeEto gradeEto = GradeMapper.mapToETO(grade);
    
    // 仅更新非空字段
    if (updateInfo.getValue() != null) {
        gradeEto.setValue(updateInfo.getValue());
    }
    if (updateInfo.getComment() != null) {
        gradeEto.setComment(updateInfo.getComment());
    }
    if (updateInfo.getSubjectEntityId() != null) {
        gradeEto.setSubjectEntityId(updateInfo.getSubjectEntityId());
    }
    
    // 后续保存及返回逻辑...
    return gradeEto;
}

方案3:遵循REST标准使用JsonPatch

使用Spring提供的JsonPatch工具,符合RFC 6902规范处理PATCH请求:

import org.springframework.data.rest.webmvc.json.JsonPatch;
import org.springframework.data.rest.webmvc.json.JsonPatchException;
import org.springframework.data.rest.webmvc.json.MappingJacksonValue;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;

@PatchMapping(path = "/grades/{id}", consumes = "application/json-patch+json")
public GradeEto partialUpdate(@PathVariable("id") final Long id, @RequestBody JsonPatch patch) {
    Grade grade = gradeRepository.findById(id).get();
    GradeEto gradeEto = GradeMapper.mapToETO(grade);
    
    try {
        MappingJacksonValue jacksonValue = new MappingJacksonValue(gradeEto);
        GradeEto patchedEto = patch.apply(jacksonValue, new MappingJackson2HttpMessageConverter().getObjectMapper());
        // 转换为实体并保存
        Grade patchedGrade = GradeMapper.mapToEntity(patchedEto);
        gradeRepository.save(patchedGrade);
        return patchedEto;
    } catch (JsonPatchException e) {
        throw new RuntimeException("Patch操作失败", e);
    }
}

此时前端需发送application/json-patch+json类型请求,格式示例:

[
    {"op": "replace", "path": "/value", "value": 2},
    {"op": "replace", "path": "/comment", "value": "Test Idea"},
    {"op": "replace", "path": "/subjectEntityId", "value": 2}
]

方案选择

  • 若需保留现有反射逻辑:选择方案1,快速适配类型转换
  • 追求类型安全与代码可读性:优先方案2,明确允许修改的字段,避免反射风险
  • 遵循REST标准规范:选择方案3,支持更丰富的PATCH操作

内容的提问来源于stack exchange,提问作者essaPL

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 05:05:25