发现问题
因项目里面同时存在fastjson与jackson两种Json处理的框架,部分对象中包含了fastjson的JSONObject类,反/序列化对象时使用的是jackson,现因一些情况需要将JSONObject为
{}(空对象)时变成null返回。
则定义了以下的方法:
public class MainTest {
public static void main(String[] args) throws JsonProcessingException {
JSONObject otherInfoForNull = null;
JSONObject otherInfo = new JSONObject();
TestDTO dto = TestDTO.builder().name("小明").age(12).sex("男").address("光明小区").birthday("2015-05-14").otherInfo(otherInfoForNull).build();
String json = objectMapper().writeValueAsString(dto);
TestDTO testDTO = objectMapper().readValue(json, TestDTO.class);
System.out.println(testDTO);
}
static ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
//忽略空Bean转json的错误
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
//忽略 在json字符串中存在,但是在java对象中不存在对应属性的情况。防止错误
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
//注册序列化提供能力的对象s
SimpleModule simpleModule = new SimpleModule();
// JsonObject
simpleModule.addDeserializer(JSONObject.class, new JSONObjectDeserializer());
mapper.registerModule(simpleModule);
return mapper;
}
}
/**
* 反序列化JSONObject
*/
class JSONObjectDeserializer extends JsonDeserializer<JSONObject> {
@Getter
private final JSONObject defaultValue;
public JSONObjectDeserializer() {
this(null);
}
public JSONObjectDeserializer(JSONObject defaultValue) {
this.defaultValue = defaultValue;
}
@Override
public JSONObject deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
String value = p.getValueAsString();
if (StringUtils.isBlank(value)) {
return this.defaultValue;
}
JSONObject result = JSON.parseObject(value);
return MapUtils.isEmpty(result) ? this.defaultValue : result;
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder(toBuilder = true)
class TestDTO {
private String name;
private Integer age;
private String sex;
private JSONObject otherInfo;
private String address;
private String birthday;
}
最开始的个人理解:
- 定义一个该类型的反序列化器
- jackson判断属性类型为JSONObject后会拿到该字段的一些上下文或字段内容来调用自定义的反序列化器
乍一看没有什么问题,来运行一下:
- 当otherInfo的值设置为otherInfoForNull时,运行如下:
TestDTO(name=小明, age=12, sex=男, otherInfo=null, address=光明小区, birthday=2015-05-14) - 当otherInfo的值设置为otherInfo时,运行如下:
TestDTO(name=小明, age=12, sex=男, otherInfo=null, address=null, birthday=null)
此时发现address、birthday的值都是空的,说明反序列化器写的有问题,这时候就需要从deserialize方法的入参着手排查下了。
问题探究
DeserializationContext
用于在反序列化过程中提供上下文信息和功能。为
JsonDeserializer提供了访问和操作反序列化过程中的各种上下文环境的方法。
- 可以获取配置信息,如日期格式、空值处理等配置
- 可以创建新的Java对象实例,并将反序列化的数据填充到该对象中
- …
这个对象不是重点,下面看一下JsonParser
JsonParser
Json解析器作用是从JSON字符串中提取出符合JSON格式要求的数据,并将其传递给后续的JsonProcessor或其他Json工具类进行进一步处理。可以编写自定义的JSON解析器,以满足不同的需求。自定义的过程可以通过实现JsonFactory中的createJsonParser方法来实现。个人感觉解析过程有点像编译原理的词法分析。
其中比较常用的方法:
- 词单元相关
- nextToken()
- currentToken()
- …
- 值相关
- getText()
- getIntValue()
- getShortValue()
- getLongValue()
- …
- 字段名相关
- getCurrentName()
- nextFieldName()
- …
public static void main(String[] args) throws IOException {
JSONObject otherInfo = null;
TestDTO dto = TestDTO.builder().name("小明").age(12).sex("男").address("光明小区").birthday("2015-05-14").otherInfo(otherInfo).build();
String json = objectMapper().writeValueAsString(dto);
JsonFactory jsonFactory = new JsonFactory();
JsonParser parser = jsonFactory.createParser(json);
while (!parser.isClosed()) {
JsonToken jsonToken = parser.nextToken();
if (null == jsonToken) {
continue;
}
System.out.printf("currentName: %s \t type: %s \t value: %s\n", parser.getCurrentName(), jsonToken.name(), parser.getText());
}
}
输出:
currentName: null type: START_OBJECT value: {
currentName: name type: FIELD_NAME value: name
currentName: name type: VALUE_STRING value: 小明
currentName: age type: FIELD_NAME value: age
currentName: age type: VALUE_NUMBER_INT value: 12
currentName: sex type: FIELD_NAME value: sex
currentName: sex type: VALUE_STRING value: 男
currentName: otherInfo type: FIELD_NAME value: otherInfo
currentName: otherInfo type: VALUE_NULL value: null
currentName: address type: FIELD_NAME value: address
currentName: address type: VALUE_STRING value: 光明小区
currentName: birthday type: FIELD_NAME value: birthday
currentName: birthday type: VALUE_STRING value: 2015-05-14
currentName: null type: END_OBJECT value: }
可以看到Json被拆解了,需要通过Token判断类型来获取字段名以及值。可以看下简单的用法
public static void main(String[] args) throws IOException {
JSONObject otherInfo = buildJSONObject();
TestDTO dto = TestDTO.builder().name("小明").age(12).sex("男").address("光明小区").birthday("2015-05-14").otherInfo(otherInfo).build();
String json = objectMapper().writeValueAsString(dto);
JsonFactory jsonFactory = new JsonFactory();
JsonParser parser = jsonFactory.createParser(json);
print(parser, "", -1, false);
}
// 初始化数据
public static JSONObject buildJSONObject() {
JSONObject otherInfo = new JSONObject();
// 年级
List<GradeInfo> gradeInfoList = new ArrayList<>();
gradeInfoList.add(GradeInfo.builder().name("一年级").teacherList(Arrays.asList(
TeacherInfo.builder().name("张老师").type("数学").idCard(IdCard.builder().number("123456789").build()).build(),
TeacherInfo.builder().name("王老师").type("语文").idCard(IdCard.builder().number("987654321").build()).build())
).build());
gradeInfoList.add(GradeInfo.builder().name("三年级").teacherList(Collections.singletonList(
TeacherInfo.builder().name("刘老师").type("英语").idCard(IdCard.builder().number("753951468").build()).build())
).build());
otherInfo.put("gradeInfoList", gradeInfoList);
otherInfo.put("idCard", IdCard.builder().number("0112244").build());
return otherInfo;
}
public static void print(JsonParser parser, String name, int arrayIndex, boolean isArray) throws IOException {
while (!parser.isClosed()) {
JsonToken jsonToken = parser.nextToken();
// 因为存在递归调用,需要给一个退出条件(token为空 || 对象结束 || 数组结束)时需要退出递归
if (jsonToken == null || JsonToken.END_OBJECT.equals(jsonToken) || JsonToken.END_ARRAY.equals(jsonToken)) {
return;
}
String currentName = name;
if (JsonToken.FIELD_NAME.equals(jsonToken)) {
parser.nextToken();
currentName = buildName(name, arrayIndex, parser);
}
if (parser.isExpectedStartObjectToken()) {
print(parser, currentName, isArray ? arrayIndex++ : -1, false);
continue;
}
if (parser.isExpectedStartArrayToken()) {
print(parser, currentName, 0, true);
continue;
}
String value = parser.getValueAsString();
System.out.printf("%s:%s\n", currentName, value);
}
}
// 构建名称
private static String buildName(String name, int arrayIndex, JsonParser parser) throws IOException {
String currentName = parser.getCurrentName();
if (StringUtils.isBlank(name)) {
return currentName;
}
// 数组
if (arrayIndex >= 0) {
return name + "[" + arrayIndex + "] -> " + currentName;
}
// 对象
return name + " -> " + currentName;
}
输出:
name:小明
age:12
sex:男
otherInfo -> idCard -> number:0112244
otherInfo -> gradeInfoList[0] -> name:一年级
otherInfo -> gradeInfoList[0] -> teacherList[0] -> name:张老师
otherInfo -> gradeInfoList[0] -> teacherList[0] -> type:数学
otherInfo -> gradeInfoList[0] -> teacherList[0] -> idCard -> number:123456789
otherInfo -> gradeInfoList[0] -> teacherList[1] -> name:王老师
otherInfo -> gradeInfoList[0] -> teacherList[1] -> type:语文
otherInfo -> gradeInfoList[0] -> teacherList[1] -> idCard -> number:987654321
otherInfo -> gradeInfoList[1] -> name:三年级
otherInfo -> gradeInfoList[1] -> teacherList[0] -> name:刘老师
otherInfo -> gradeInfoList[1] -> teacherList[0] -> type:英语
otherInfo -> gradeInfoList[1] -> teacherList[0] -> idCard -> number:753951468
address:光明小区
birthday:2015-05-14
问题解决
这样看下来,仅仅使用JsonParser的getValueAsString()方法是没有办法获取到当前完整json的,但也不用像上面那样复杂的去解析,jackson有另一种方法可以直接拿到完整json。
static class JSONObjectDeserializer extends JsonDeserializer<JSONObject> {
@Getter
private final JSONObject defaultValue;
public JSONObjectDeserializer() {
this(null);
}
public JSONObjectDeserializer(JSONObject defaultValue) {
this.defaultValue = defaultValue;
}
@Override
public JSONObject deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
// 重点拿到json树转为string!!
JsonNode treeNode = p.getCodec().readTree(p);
String value = treeNode.toString();
if (StringUtils.isBlank(value)) {
return this.defaultValue;
}
JSONObject result = JSON.parseObject(value);
return MapUtils.isEmpty(result) ? this.defaultValue : result;
}
}
将反序列化方法改为上面的即可解决问题。
反思
出现这种问题主要是自己对jackson框架了解不深,该框架功能非常强大,不能停留在简单的使用,需要进一步提高自身的熟练程度。
文章讨论了一个项目中同时使用fastjson和jackson导致的问题,当JSONObject为空时希望转换为null。作者定义了一个自定义的JsonDeserializer,但在初始尝试中出现了其他字段丢失的问题。通过深入理解JsonParser和DeserializationContext,作者发现可以通过JsonParser的getCodec().readTree(p)获取完整的JsonNode,然后转换为字符串,从而正确处理空JSONObject。最后,作者反思需要更深入地学习和理解Jackson框架。
2224

被折叠的 条评论
为什么被折叠?



