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

相关推荐
学长毕设8 分钟前
基于SpringBoot的野生动物公益保护平台设计与实现-附源码
java·spring boot·后端
梦里寻码29 分钟前
我用 NestJS + Vue3 写了一套完整的研发管理系统(58 表 / 229 API / 130 项断言,全开源
前端·后端·算法
鶴哥只手遮天37 分钟前
《Redis 8.10.3 深度剖析》系列 ·第 10 章 Hash 一:`listpack`、`hashtable` 与 `LISTPACK_EX`
redis·后端·开源
SL_staff41 分钟前
离散制造排程破局:JVS-APS 四步闭环落地实践(开发者视角)
java·产品·全栈
johnjincaaaa41 分钟前
WebAssembly 逆向实战:从 0 搞懂猿人学第 15 题
后端
Gopher_HBo1 小时前
Go CPU 剖析:四角色白话机制 + 实测实验
后端
这个昵称不容易1 小时前
SpringBoot 美容院管理系统:232个Java文件315个接口,5套角色从预约到发货一整条链路
java·springboot·毕设
猿人谷1 小时前
Surrogate Model:让进化算法学会“预测”,为什么它正在成为智能优化的重要技术?
后端·算法·架构
装不满的克莱因瓶2 小时前
【SpringAI Alibaba】ReactAgent记忆管理MessageTrimmingHook与MessageDeletionHook
java·人工智能·ai·ai编程·springai·springaialibaba
the局外人2 小时前
LangChain ToolRuntime 入门:context、state 与 store 的实际用法
后端·langchain·agent