You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot 3.0升级后Jackson反序列化空对象至Enum遇异常求助

问题:Spring Boot 3.0(Jackson 2.17)枚举反序列化空对象失败

将Spring Boot应用从2.7.x升级到3.0.x后,反序列化包含空对象{}的枚举集合字段时出现失败。同样的代码在Jackson 2.13中正常运行,但Jackson 2.17中抛出类型不匹配异常。需要实现一个支持任意枚举类的自定义反序列化器,同时满足以下约束:

  • POJO类由第三方提供,无法修改JSON结构或在字段上添加@JsonDeserialize注解
  • 不能用正则替换JSON中的空对象,避免引入全局风险
  • 不能为每个枚举类单独注册模块,要适配未来新增的枚举类

JSON示例

{       
    "lineItems":[
        {},
        {}
    ]
}

实体类

@ToString
@Data
public class YourClass {
    private List<ItemEnum> lineItems;
}

枚举类

public enum ItemEnum {
    ITEM_1, ITEM_2, ITEM_3
}

测试代码

public static void main(String[] args) throws JsonMappingException, JsonProcessingException {
    // 修正原测试代码中的JSON格式错误
    String json = "{\"lineItems\":[{},{}]}";

    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.enable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL);
    objectMapper.coercionConfigFor(LogicalType.Array)
            .setCoercion(CoercionInputShape.EmptyString, CoercionAction.AsNull)
            .setCoercion(CoercionInputShape.EmptyObject, CoercionAction.AsNull)
            .setCoercion(CoercionInputShape.EmptyArray, CoercionAction.AsNull);
    objectMapper.coercionConfigFor(LogicalType.Collection)
            .setCoercion(CoercionInputShape.EmptyString, CoercionAction.AsNull)
            .setCoercion(CoercionInputShape.EmptyObject, CoercionAction.AsNull)
            .setCoercion(CoercionInputShape.EmptyArray, CoercionAction.AsNull);
    objectMapper.coercionConfigFor(LogicalType.Enum)
            .setCoercion(CoercionInputShape.EmptyString, CoercionAction.AsNull)
            .setCoercion(CoercionInputShape.EmptyObject, CoercionAction.AsNull)
            .setCoercion(CoercionInputShape.EmptyArray, CoercionAction.AsNull);
    YourClass obj = objectMapper.readValue(json, YourClass.class);
    System.err.println(obj);
}

错误信息

Exception in thread "main" com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type `com.demo.ItemEnum` from Object value (token `JsonToken.START_OBJECT`)
 at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 5, column: 3] (through reference chain: com.demo.YourClass["lineItems"]->java.util.ArrayList[0])
    at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:59)
    at com.fasterxml.jackson.databind.DeserializationContext.reportInputMismatch(DeserializationContext.java:1767)
    at com.fasterxml.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1541)
    at com.fasterxml.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1446)
    at com.fasterxml.jackson.databind.DeserializationContext.extractScalarFromObject(DeserializationContext.java:958)
    at com.fasterxml.jackson.databind.deser.std.EnumDeserializer.deserialize(EnumDeserializer.java:290)
    at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer._deserializeFromArray(CollectionDeserializer.java:361)
    at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:246)
    at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:30)
    at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:129)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:310)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:177)
    at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:342)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4905)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3848)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3816)
    at com.demo.Application.main(Application.java:57)

解决方案:通用枚举空对象反序列化器

1. 实现通用枚举反序列化器

继承Jackson原生的EnumDeserializer,重写deserialize方法,遇到空对象时直接返回null,其他场景保留原有枚举反序列化逻辑:

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonToken;
import com.fasterxml.jackson.databind.deser.std.EnumDeserializer;
import com.fasterxml.jackson.databind.type.EnumType;

import java.io.IOException;

public class UniversalEnumEmptyObjectDeserializer extends EnumDeserializer {

    public UniversalEnumEmptyObjectDeserializer(EnumType enumType) {
        super(enumType);
    }

    @Override
    public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        // 检测到空对象时返回null,并跳过整个空对象的token流
        if (p.getCurrentToken() == JsonToken.START_OBJECT) {
            p.skipChildren();
            return null;
        }
        // 正常枚举值反序列化逻辑
        return super.deserialize(p, ctxt);
    }
}

2. 注册全局反序列化模块

创建自定义模块,通过BeanDeserializerModifier为所有枚举类型统一替换反序列化器,无需逐个枚举注册:

import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.type.EnumType;

public class EnumEmptyObjectModule extends SimpleModule {

    @Override
    public void setupModule(SetupContext context) {
        super.setupModule(context);
        context.addBeanDeserializerModifier(new com.fasterxml.jackson.databind.deser.BeanDeserializerModifier() {
            @Override
            public JsonDeserializer<?> modifyEnumDeserializer(DeserializationConfig config, EnumType type,
                                                              BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
                // 替换为自定义的通用枚举反序列化器
                return new UniversalEnumEmptyObjectDeserializer(type);
            }
        });
    }
}

3. 修改测试代码验证

在ObjectMapper中注册自定义模块,原有coercion配置可移除(自定义反序列化器已处理空对象场景):

public static void main(String[] args) throws JsonMappingException, JsonProcessingException {
    String json = "{\"lineItems\":[{},{}]}";

    ObjectMapper objectMapper = new ObjectMapper();
    // 保留未知枚举值转为null的配置
    objectMapper.enable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL);
    // 注册全局自定义模块
    objectMapper.registerModule(new EnumEmptyObjectModule());
    
    YourClass obj = objectMapper.readValue(json, YourClass.class);
    System.err.println(obj); // 输出:YourClass(lineItems=[null, null])
}

方案说明

  • 完全满足约束:无需修改第三方POJO、无需正则修改JSON、全局适配所有现有及未来新增的枚举类
  • 兼容性:仅拦截空对象场景,不影响正常枚举值的反序列化逻辑
  • 稳定性:通过skipChildren()跳过空对象的token流,避免后续解析出现异常

内容的提问来源于stack exchange,提问作者Swathi Ragupathi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 05:45:02