FastJSON 默认行为:JSON.toJSONString 忽略 null 字段
完整的 FakeRegistrationController
代码,这让我可以全面分析后端逻辑,特别是为什么空的字段(如 compareDate
)不返回给前端。我将详细分析代码的每个接口,尤其是与 list
请求和字段返回相关的部分,并解释原因。
代码结构总览
FakeRegistrationController
是一个 RESTful 控制器,提供了三个接口:
POST /fakeRegistration/registration/save
:保存或修改假货登记信息。POST /fakeRegistration/compare/save
:保存或修改比对结果。GET /fakeRegistration/list
:获取假货登记列表(对应前端的qlist
请求)。
我们关心的是 list
接口,因为它直接决定了前端表格接收到的数据内容,特别是为何空的 compareDate
不返回。
详细分析 list
接口
接口定义
@GetMapping("list")
@ApiOperation("假货登记列表")
public BaseResult list(@ApiIgnore @SessionAttribute(Constants.ADMIN_ID) Integer adminId,
BasePage page) {
Integer vipAdminId = copywritingApiService.getVipAdminId(adminId, 11);
Page<FakeRegistration> fakeRegistrationPage = fakeRegistrationService.findByAdminId(vipAdminId, page);
List<FakeRegistration> fakeRegistrations = fakeRegistrationPage.getContent();
List<FakeRegistrationListDTO> fakeRegistrationListDTOS = new ArrayList<>();
for (FakeRegistration fakeRegistration: fakeRegistrations) {
FakeRegistrationListDTO fakeRegistrationListDTO = JSON.parseObject(JSON.toJSONString(fakeRegistration), FakeRegistrationListDTO.class);
if (null != fakeRegistration.getProductId()) {
Product product = productService.findById(fakeRegistration.getProductId()).orElseThrow(() -> new RuntimeException("未找到商品信息"));
fakeRegistrationListDTO.setProductName(product.getName());
}
Admin creator = adminService.findById(fakeRegistration.getCreatorId()).orElseThrow(() -> new RuntimeException("未找到创建人信息"));
fakeRegistrationListDTO.setCreatorName(StringUtils.isEmpty(creator.getNickname()) ? creator.getUsername() : creator.getNickname());
fakeRegistrationListDTO.setGenuineIdentificationPoints(fakeRegistrationApiService.findIdentification(fakeRegistration.getId(), 1));
fakeRegistrationListDTO.setFakeIdentificationPoints(fakeRegistrationApiService.findIdentification(fakeRegistration.getId(), 0));
fakeRegistrationListDTOS.add(fakeRegistrationListDTO);
}
return BaseResult.success(new PageImpl<>(fakeRegistrationListDTOS, PageRequest.of(fakeRegistrationPage.getNumber(), fakeRegistrationPage.getSize()), fakeRegistrationPage.getTotalElements()));
}
数据流分析
-
数据查询:
fakeRegistrationService.findByAdminId(vipAdminId, page)
返回一个Page<FakeRegistration>
,其中FakeRegistration
是数据库实体类,包含所有字段(如id
,createdDate
,compareDate
,comparisonStatus
等)。fakeRegistrations
是分页内容的List<FakeRegistration>
。
-
数据转换:
- 每个
FakeRegistration
被转换为FakeRegistrationListDTO
:FakeRegistrationListDTO fakeRegistrationListDTO = JSON.parseObject(JSON.toJSONString(fakeRegistration), FakeRegistrationListDTO.class);
- 这里使用了 FastJSON(
com.alibaba.fastjson.JSON
)进行序列化和反序列化:JSON.toJSONString(fakeRegistration)
将FakeRegistration
转为 JSON 字符串。JSON.parseObject(..., FakeRegistrationListDTO.class)
将 JSON 字符串转为FakeRegistrationListDTO
对象。
- 然后手动补充了:
productName
:从ProductService
获取。creatorName
:从AdminService
获取。genuineIdentificationPoints
和fakeIdentificationPoints
:从FakeRegistrationApiService
获取。
- 每个
-
返回结果:
- 返回一个
PageImpl<FakeRegistrationListDTO>
,最终被序列化为 JSON 响应。
- 返回一个
为什么空的字段(如 compareDate
)不返回?
-
FastJSON 的默认行为:
- FastJSON 在序列化时,默认忽略
null
值字段,除非显式配置SerializerFeature.WriteMapNullValue
。 - 如果
FakeRegistration
的compareDate
是null
,JSON.toJSONString(fakeRegistration)
生成的 JSON 字符串不会包含"compareDate"
。 - 示例:
FakeRegistration fr = new FakeRegistration(); fr.setId(34); fr.setComparisonStatus(1); fr.setCompareDate(null); // 为空 String json = JSON.toJSONString(fr); // 输出: {"id":34,"comparisonStatus":1}
- 反序列化到
FakeRegistrationListDTO
时,由于 JSON 中没有compareDate
,fakeRegistrationListDTO
的compareDate
字段不会被赋值,最终返回的 JSON 也不会包含它。
- FastJSON 在序列化时,默认忽略
-
业务逻辑:
- 从其他接口看,
compareDate
只有在compare/save
接口保存比对结果时才会设置:fakeRegistrationOrigin.setCompareDate(new Date());
- 在
registration/save
接口中,新建记录时没有设置compareDate
,它保持为null
。 - 因此,未完成比对的记录(
comparisonStatus = 0
或1
)在数据库中compareDate
就是NULL
,序列化后被忽略。
- 从其他接口看,
-
DTO 定义的影响:
- 如果
FakeRegistrationListDTO
中定义了compareDate
:public class FakeRegistrationListDTO { private Integer id; private Date compareDate; // 假设是这样 // 其他字段 }
- 当
JSON.parseObject
处理没有compareDate
的 JSON 时,fakeRegistrationListDTO.compareDate
会是null
,但后续的序列化(返回给前端时)仍由 FastJSON 处理,又会被忽略。
- 如果
其他接口的补充分析
1. registration/save
@PostMapping("registration/save")
public BaseResult save(@RequestBody FakeRegistration fakeRegistration, ...) {
if(null != fakeRegistration.getId()) {
FakeRegistration fakeRegistrationOrigin = fakeRegistrationService.findById(fakeRegistration.getId()).orElseThrow(...);
fakeRegistration = SqlUtil.mergeObject(fakeRegistration, fakeRegistrationOrigin);
if(null != fakeRegistration.getCompareResult() && fakeRegistration.getCompareResult() == 1) {
fakeRegistrationOrigin.setComparisonStatus(3);
} else if(null != fakeRegistration.getCompareResult() && fakeRegistration.getCompareResult() == 0) {
fakeRegistrationOrigin.setComparisonStatus(2);
}
} else {
fakeRegistration.setAdminId(vipAdminId);
fakeRegistration.setCreatorId(adminId);
}
fakeRegistration = fakeRegistrationService.save(fakeRegistration);
...
}
- 关键点:
- 新建记录时(
fakeRegistration.getId() == null
),只设置了adminId
和creatorId
,没有初始化compareDate
,它默认为null
。 - 修改记录时,如果提供了
compareResult
,会更新comparisonStatus
,但不会设置compareDate
。 - 因此,
compareDate
在这个接口中始终不会被赋值。
- 新建记录时(
2. compare/save
@PostMapping("compare/save")
public BaseResult compareSave(@RequestBody FakeRegistrationSaveDTO fakeRegistration, ...) {
FakeRegistration fakeRegistrationOrigin = fakeRegistrationService.findById(fakeRegistration.getId()).orElseThrow(...);
fakeRegistrationOrigin.setCompareResult(fakeRegistration.getCompareResult());
fakeRegistrationOrigin.setCompareDate(new Date());
fakeRegistrationService.save(fakeRegistrationOrigin);
...
}
- 关键点:
- 这里显式设置了
compareDate = new Date()
,表示比对完成的时间。 - 只有通过这个接口保存比对结果时,
compareDate
才会被赋值。 - 这解释了前端数据中,只有
comparisonStatus = 2
或更高状态的记录有compareDate
。
- 这里显式设置了
综合结论:为什么空的字段不返回?
-
数据来源:
compareDate
在数据库中默认为NULL
,除非通过compare/save
接口保存比对结果时才会被赋值。- 未完成比对的记录(
comparisonStatus = 0
或1
)没有compareDate
值。
-
FastJSON 序列化:
list
接口中,JSON.toJSONString(fakeRegistration)
将FakeRegistration
转为 JSON 时,默认忽略null
字段。- 如果
compareDate
是null
,它不会出现在 JSON 字符串中。 - 反序列化到
FakeRegistrationListDTO
后,compareDate
仍未被赋值,最终返回的 JSON 也不包含这个字段。
-
业务逻辑:
- 设计上,
compareDate
只有在比对完成时才有意义,未完成时保持null
并被忽略是合理的。
- 设计上,
-
最终返回:
- Spring Boot 的
@RestController
使用 Jackson(或其他配置)将PageImpl<FakeRegistrationListDTO>
序列化为 JSON,同样忽略null
字段,进一步确保空的compareDate
不出现在响应中。
- Spring Boot 的
前端数据的验证
你的前端数据:
id: 34
,33
,32
(comparisonStatus = 0
或1
):没有compareDate
,因为未调用compare/save
。id: 31
,30
,28
(comparisonStatus = 2
):有compareDate
,因为通过compare/save
设置了值。
改进建议
如果希望空的 compareDate
也返回(例如 "compareDate": null
):
-
修改 FastJSON 配置:
FakeRegistrationListDTO fakeRegistrationListDTO = JSON.parseObject( JSON.toJSONString(fakeRegistration, SerializerFeature.WriteMapNullValue), FakeRegistrationListDTO.class );
- 添加
WriteMapNullValue
,确保null
字段被包含。
- 添加
-
调整 DTO:
- 在
FakeRegistrationListDTO
中显式处理:fakeRegistrationListDTO.setCompareDate(fakeRegistration.getCompareDate() != null ? fakeRegistration.getCompareDate() : null);
- 在
-
全局配置:
- 在 Spring Boot 中配置 Jackson:
@Bean public ObjectMapper objectMapper() { ObjectMapper mapper = new ObjectMapper(); mapper.setSerializationInclusion(JsonInclude.Include.ALWAYS); return mapper; }
- 在 Spring Boot 中配置 Jackson:
总结
空的 compareDate
不返回给前端是因为:
- 业务逻辑:未完成比对时,数据库中
compareDate
为NULL
。 - FastJSON 默认行为:
JSON.toJSONString
忽略null
字段。 - 设计选择:后端选择不返回无意义的空字段。
这种行为是合理的,但如果前端需要一致性,可以通过上述方式调整后端返回。