如何区分JSON请求中未传递可选值与显式值?Java字段赋值难题
在区分JSON请求中未传递的可选值与显式传递的值时遇到了问题:gmxClient.java中的buildLevel和securityClass被定义为int类型字段,当POST请求的JSON对象不包含其中某个字段时,该字段会被自动赋值为0,进而覆盖数据库中原有值。
举个例子:
原有数据buildLevel=3、securityClass=1,发送以下JSON请求后,securityClass会被覆盖为0:
{ "id": 00039281, "buildLevel": 5 }
相关代码如下:
gmxClientFacadeREST.java
@POST @Path("{clientId}") @Consumes({MediaType.APPLICATION_JSON}) public Response editPost(@PathParam("clientId") Long clientId, gmxClient old) { try { gmxClient new = super.find(id); new.update(old); super.edit(new); return Response .status(Response.Status.OK) .build(); } catch (Exception e) { return Response .status(Response.Status.BAD_REQUEST) .build(); } }
gmxClient.java
public void update(gmxClient newEntity) { if (newEntity.getBuildLevel() != null) setBuildLevel(newEntity.getBuildLevel()); // 抛出异常,int类型永远不为null if (newEntity.getSecurityClass() != null) setSecurityClass(newEntity.getSecurityClass()); // 抛出异常,int类型永远不为null }
方案1:将基本类型改为包装类型
把gmxClient中的buildLevel和securityClass字段从int改为Integer,这样JSON未传递字段时,字段值会是null而非默认的0,就能通过null判断决定是否覆盖原有值。
修改gmxClient.java的字段定义及对应方法:
private Integer buildLevel; private Integer securityClass; // getter和setter同步改为Integer类型 public Integer getBuildLevel() { return buildLevel; } public void setBuildLevel(Integer buildLevel) { this.buildLevel = buildLevel; } public Integer getSecurityClass() { return securityClass; } public void setSecurityClass(Integer securityClass) { this.securityClass = securityClass; }
更新update方法逻辑:
public void update(gmxClient newEntity) { if (newEntity.getBuildLevel() != null) { setBuildLevel(newEntity.getBuildLevel()); } if (newEntity.getSecurityClass() != null) { setSecurityClass(newEntity.getSecurityClass()); } }
方案2:利用JSON序列化框架注解控制反序列化
如果不想修改字段类型,可借助Jackson等JSON框架的注解,让框架只处理请求中明确传递的字段,忽略未传递的属性。
比如在gmxClient的字段上添加@JsonProperty(required = false),同时在类上添加@JsonIgnoreProperties(ignoreUnknown = true)避免未知字段干扰,确保未传递的字段保留原有值。
方案3:创建专用更新DTO
定义一个和业务实体gmxClient对应的DTO类,将可选字段设为包装类型,接收请求时先序列化为DTO,再根据DTO中的非null字段更新实体类属性。
示例DTO:
public class GmxClientUpdateDTO { private Integer buildLevel; private Integer securityClass; // getter和setter public Integer getBuildLevel() { return buildLevel; } public void setBuildLevel(Integer buildLevel) { this.buildLevel = buildLevel; } public Integer getSecurityClass() { return securityClass; } public void setSecurityClass(Integer securityClass) { this.securityClass = securityClass; } }
修改REST接口逻辑:
@POST @Path("{clientId}") @Consumes({MediaType.APPLICATION_JSON}) public Response editPost(@PathParam("clientId") Long clientId, GmxClientUpdateDTO updateDTO) { try { gmxClient entity = super.find(clientId); if (updateDTO.getBuildLevel() != null) { entity.setBuildLevel(updateDTO.getBuildLevel()); } if (updateDTO.getSecurityClass() != null) { entity.setSecurityClass(updateDTO.getSecurityClass()); } super.edit(entity); return Response.status(Response.Status.OK).build(); } catch (Exception e) { return Response.status(Response.Status.BAD_REQUEST).build(); } }
方案4:使用映射工具实现选择性更新
借助MapStruct这类映射工具,配置仅映射非null的字段,自动跳过未传递的属性。只需在映射接口中添加条件注解或配置,即可实现基于DTO的选择性更新,无需手动判断每个字段。
内容的提问来源于stack exchange,提问作者yoss

