Jackson 自定义反序列化器的类型不匹配陷阱

背景

在 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 类型,保证行为与默认反序列化器一致。

相关推荐
一条破秋裤6 分钟前
Linux 线程创建:pthread_create 与基本回收
java·linux·运维
布吉岛的石头14 分钟前
Java 程序员第 49 阶段5:BERT 预训练目标 MLM+NSP 的工程含义
java·人工智能·深度学习·bert·transformer
摇滚侠1 小时前
《On Java 中文版 基础卷》阅读笔记 对象无处不在 03
java·笔记·python
二十雨辰1 小时前
[学成在线]-01项目背景
java
IT_陈寒2 小时前
Java线程池这破玩意,差点让我周末加班排查到凌晨
前端·人工智能·后端
弈栈录2 小时前
Java AI 应用的异步化与高并发设计
java·后端·架构
落魄实习生3 小时前
Agent Scope Java 2.x 系列【2】 ReActAgent
java·开发语言
艸肅3 小时前
Ubuntu 安装 JDK (含手动安装)
java·ubuntu·jdk
m0_734172423 小时前
Python字典计数怎样处理首次出现的键
java
凤山老林3 小时前
Spring Boot 集成 iText 7 实现动态 PDF 生成与电子签章:合同、报表场景实战
spring boot·后端·pdf·itext7·电子签章