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

