背景
在 Jackson 中,当我们需要自定义 String 类型的反序列化逻辑时,如果不注意 token 类型的校验,就会引入一个隐蔽的缺陷------类型不匹配被悄悄吞掉,变成 null。
代码
SimpleStringDeserializer.java
java
public class SimpleStringDeserializer extends JsonDeserializer<String> {
@Override
public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
// TODO
// if (p.currentToken() != JsonToken.VALUE_STRING) {
// throw MismatchedInputException.from(p, String.class, "...");
// }
String value = p.getValueAsString();
if (value == null) {
return null;
}
return p.getValueAsString();
}
}
Main.java
java
public class Main {
public static void main(String[] args) throws Exception {
testWithoutDeserializer();
testWithDeserializer();
}
static void testWithoutDeserializer() {
ObjectMapper mapper = new ObjectMapper();
System.out.println("=== Without SimpleStringDeserializer ===\n");
String[] cases = {
"{\"name\": [1,2]}",
};
for (String json : cases) {
System.out.println("Input: " + json);
try {
User user = mapper.readValue(json, User.class);
System.out.println("Result: " + user.getName());
} catch (Exception e) {
System.out.println("Error: " + e.getClass().getSimpleName());
}
System.out.println();
}
}
static void testWithDeserializer() {
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule("SimpleStringModule");
module.addDeserializer(String.class, new SimpleStringDeserializer());
mapper.registerModule(module);
System.out.println("=== With SimpleStringDeserializer ===\n");
String[] cases = {
"{\"name\": [1,2]}",
};
for (String json : cases) {
System.out.println("Input: " + json);
try {
User user = mapper.readValue(json, User.class);
System.out.println("Result: " + user.getName());
} catch (Exception e) {
System.out.println("Error: " + e.getClass().getSimpleName());
}
System.out.println();
}
}
static class User {
private String name;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
}
运行结果
情况一:TODO 代码注释掉(不校验 token 类型)
ini
=== Without SimpleStringDeserializer ===
Input: {"name": [1,2]}
Error: MismatchedInputException
=== With SimpleStringDeserializer ===
Input: {"name": [1,2]}
Result: null
情况二:TODO 代码未注释(校验 token 类型)
ini
=== Without SimpleStringDeserializer ===
Input: {"name": [1,2]}
Error: MismatchedInputException
=== With SimpleStringDeserializer ===
Input: {"name": [1,2]}
Error: MismatchedInputException
结论
当 Jackson 解析到 "name": [1,2] 时,当前 token 是 START_ARRAY。getValueAsString() 对非字符串 token 的处理策略是返回 null 而非抛异常 ,所以自定义反序列化器把类型不匹配的情况悄悄吞掉了,变成了 null。而 Jackson 默认的 String 反序列化器内部会检查 token 类型,遇到 START_ARRAY 会主动抛出 MismatchedInputException。
这其实是自定义反序列化器的一个缺陷------应该在调用 getValueAsString() 之前先校验 token 类型,保证行为与默认反序列化器一致。