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

Jackson中@JsonSubTypes致Integer序列化为字符串的问题求助

问题描述

在处理lottieFile与dotLottie JSON的解析和序列化场景中,使用Java 17 + Jackson 2.14.1遇到@JsonSubTypes的序列化异常:

通过以下注解配置类型鉴别后,原本为数字的属性a在序列化回JSON时被转为字符串格式:

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "a")
@JsonSubTypes({
      @JsonSubTypes.Type(names = {"0", "0.0"}, value = FixedBezier.class),
      @JsonSubTypes.Type(names = {"1", "1.0"}, value = AnimatedBezier.class)
})

测试对比:

Original:     {"a": 0, "d": "test"}
Generated:    {"a": "0", "d": "test"}

尝试给a字段添加@JsonSerialize(using = IntegerSerializer.class)或自定义序列化器后,问题依然存在。需要实现的目标是:让JsonSubType的类型标识值强制以数字形式存储在JSON中。

完整测试代码如下:

import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;

import java.io.IOException;

public class BezierTest {

    private static final ObjectMapper mapper = new ObjectMapper();

    public static void main(String[] args) {
        String[] jsons = new String[]{
                // jsonLottieFilesFixed
                "{\"a\":0,\"d\":\"test\"}",
                // jsonLottieFilesAnimated
                "{\"a\":1,\"d\":\"test\"}",
                // dotLottieFilesFixed
                "{\"a\":0.0,\"d\":\"test\"}",
                // dotLottieFilesAnimated
                "{\"a\":1.0,\"d\":\"test\"}"
        };
        for (String json : jsons) {
            test(json);
        }
    }

    private static void test(String json) {
        BaseBezier objectFromJson = null;
        try {
            objectFromJson = mapper.readValue(json, BaseBezier.class);
            ObjectMapper mapper = new ObjectMapper();
            String jsonFromObject = mapper.writeValueAsString(objectFromJson);

            System.out.println("Original:\t" + json);
            System.out.println("Generated:\t" + jsonFromObject);

            System.out.println("Is equal: " + json.equals(jsonFromObject));
        } catch (JsonProcessingException e) {
            System.err.println("Json exception: " + e.getMessage());
        }
    }

    @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "a")
    @JsonSubTypes({
            @JsonSubTypes.Type(names = {"0", "0.0"}, value = FixedBezier.class),
            @JsonSubTypes.Type(names = {"1", "1.0"}, value = AnimatedBezier.class)
    })
    interface BaseBezier {

    }

    @JsonIgnoreProperties(ignoreUnknown = true)
    @JsonInclude(JsonInclude.Include.NON_NULL)
    record FixedBezier(
            @JsonProperty("a")
            @JsonSerialize(using = AnimatedSerializer.class)
            Integer animated,

            @JsonProperty("d")
            String data

    ) implements BaseBezier {
    }

    @JsonIgnoreProperties(ignoreUnknown = true)
    @JsonInclude(JsonInclude.Include.NON_NULL)
    public record AnimatedBezier(
            @JsonProperty("a")
            // @JsonSerialize(using = AnimatedSerializer.class)
            Integer animated,

            @JsonProperty("d")
            String data
    ) implements BaseBezier {
    }

    private static class AnimatedSerializer extends JsonSerializer {
        @Override
        public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
            jsonGenerator.writeNumber((int) o);
        }
    }
}
解决方案

问题核心在于:Jackson的@JsonTypeInfo默认会将类型标识(即@JsonSubTypes.Type中的names值)按字符串序列化,且类型标识的处理优先级高于字段自身的序列化配置,导致自定义序列化器无法生效。

要解决这个问题,需要通过自定义类型ID解析器来接管类型标识的处理逻辑,确保序列化时输出数字格式。具体步骤如下:

1. 实现自定义TypeIdResolver

这个类负责完成Java类型与JSON数字标识的双向映射,同时兼容输入时的整数(0/1)和浮点数(0.0/1.0)格式:

import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.DatabindContext;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.jsontype.TypeIdResolver;
import com.fasterxml.jackson.databind.type.TypeFactory;

import java.io.IOException;

public class BezierTypeIdResolver implements TypeIdResolver {
    private JavaType baseType;

    @Override
    public void init(JavaType baseType) {
        this.baseType = baseType;
    }

    @Override
    public String idFromValue(Object value) {
        // 返回数字字符串,后续会被转为JSON数字
        return value instanceof FixedBezier ? "0" : "1";
    }

    @Override
    public String idFromValueAndType(Object value, Class<?> suggestedType) {
        return idFromValue(value);
    }

    @Override
    public JavaType typeFromId(DatabindContext context, String id) throws IOException {
        // 兼容输入的数字字符串和浮点数字符串
        if ("0".equals(id) || "0.0".equals(id)) {
            return TypeFactory.defaultInstance().constructSpecializedType(baseType, FixedBezier.class);
        } else if ("1".equals(id) || "1.0".equals(id)) {
            return TypeFactory.defaultInstance().constructSpecializedType(baseType, AnimatedBezier.class);
        }
        throw new IOException("Unknown type id: " + id);
    }

    @Override
    public JsonTypeInfo.Id getMechanism() {
        return JsonTypeInfo.Id.CUSTOM;
    }
}

2. 修改BaseBezier接口的类型注解

替换原有的@JsonTypeInfo和@JsonSubTypes配置,使用自定义的类型ID解析器:

@JsonTypeInfo(use = JsonTypeInfo.Id.CUSTOM, property = "a", visible = true)
@JsonTypeIdResolver(BezierTypeIdResolver.class)
interface BaseBezier {
}
  • visible = true:确保Jackson在解析时保留a字段的值,让子类的animated属性能正常赋值,同时不干扰类型鉴别逻辑。

3. 调整ObjectMapper配置

开启Jackson的WRITE_TYPE_IDS_AS_NUMBERS特性,让类型标识以数字形式输出:

private static final ObjectMapper mapper = new ObjectMapper()
        .configure(com.fasterxml.jackson.databind.SerializationFeature.WRITE_TYPE_IDS_AS_NUMBERS, true);

4. 移除无效的自定义序列化器

删除子类字段上的@JsonSerialize(using = AnimatedSerializer.class)注解,因为类型标识的处理已经由自定义TypeIdResolver接管,字段自身的序列化会正常执行。

修改后的子类示例

@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
record FixedBezier(
        @JsonProperty("a")
        Integer animated,

        @JsonProperty("d")
        String data
) implements BaseBezier {
}

@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public record AnimatedBezier(
        @JsonProperty("a")
        Integer animated,

        @JsonProperty("d")
        String data
) implements BaseBezier {
}

最终效果

修改完成后,序列化输出的JSON中a字段会以数字形式呈现,同时依然能正确解析输入时的整数和浮点数格式,测试结果如下:

Original:     {"a":0,"d":"test"}
Generated:    {"a":0,"d":"test"}
Is equal: true

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 01:10:29