1. 概述

本文将探讨如何强制 Jackson 将JSON值反序列化为指定类型。

默认情况下,Jackson会根据目标字段的类型反序列化JSON值。但有时目标字段类型可能不够具体,这是为了允许多种类型的值。这种情况下,Jackson可能会选择指定类型的最近匹配子类型进行反序列化,可能导致意外结果。

我们将学习如何限制Jackson将JSON值反序列化为特定类型。

2. 代码示例准备

我们将定义一个包含多类型值字段的JSON结构,创建对应的Java类,并在特定情况下强制Jackson将值反序列化为指定类型。

2.1. 依赖项

首先在 pom.xml 中添加 Jackson Databind 依赖:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.17.2</version>
</dependency>

2.2. JSON结构

输入JSON结构如下:

{
  "person": [
    {
      "key": "name",
      "value": "John"
    },
    {
      "key": "id",
      "value": 25
    }
  ]
}

这里 person 对象包含多个键值对属性,value 字段可以接受不同类型的值。

2.3. DTO

创建 DTO 类表示JSON结构:

public class PersonDTO {
    private List<KeyValuePair> person;

    // 构造方法、getter和setter
    
    public static class KeyValuePair {
        private String key;
        private Object value;

        // 构造方法、getter和setter
    }
}

PersonDTO 包含表示人员信息的键值对列表,value 字段设为 Object 类型以支持多种值类型。

3. 默认反序列化行为

先看Jackson的默认反序列化机制。

3.1. 读取JSON

public PersonDTO readJson(String json) throws JsonProcessingException {
    ObjectMapper mapper = new ObjectMapper();
    return mapper.readValue(json, PersonDTO.class);
}

使用 ObjectMapper 将JSON字符串转换为 PersonDTO 对象。我们期望 id 值反序列化为 Long 类型。

3.2. 测试默认行为

测试方法并验证字段类型:

@Test
void givenJsonWithDifferentValueTypes_whenDeserialize_thenIntValue() throws JsonProcessingException {
    String json = "{\"person\": [{\"key\": \"name\", \"value\": \"John\"}, {\"key\": \"id\", \"value\": 25}]}";
    PersonDTO personDTO = readJson(json);
    assertEquals(String.class, personDTO.getPerson().get(0).getValue().getClass());
    assertEquals(Integer.class, personDTO.getPerson().get(1).getValue().getClass()); // 默认是Integer
}

测试通过,但有个坑:*Jackson将 id 值反序列化为 Integer 而非 Long,因为该值能被Integer容纳。*

接下来我们将修改这个默认行为。

4. 自定义反序列化为指定类型

最简单的方式是使用自定义反序列化器。

KeyValuePair 类的 value 字段创建自定义反序列化器

public class ValueDeserializer extends JsonDeserializer<Object> {
    @Override
    public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        JsonToken currentToken = p.getCurrentToken();
        if (currentToken == JsonToken.VALUE_NUMBER_INT) {
            return p.getLongValue();
        } else if (currentToken == JsonToken.VALUE_STRING) {
            return p.getText();
        } 
        return null;
    }
}

JsonParser 获取当前token,判断是数字还是字符串。如果是数字则返回 Long 类型,字符串则返回 String 类型,强制将数值反序列化为 long

然后在 KeyValuePair 类的 value 字段添加 @JsonDeserialize 注解

public static class KeyValuePair {
    private String key;

    @JsonDeserialize(using = ValueDeserializer.class)
    private Object value;
}

编写测试验证返回 Long 值:

@Test
void givenJsonWithDifferentValueTypes_whenDeserialize_thenLongValue() throws JsonProcessingException {
    String json = "{\"person\": [{\"key\": \"name\", \"value\": \"John\"}, {\"key\": \"id\", \"value\": 25}]}";
    PersonDTOWithCustomDeserializer personDTO = readJsonWithCustomDeserializer(json);
    assertEquals(String.class, personDTO.getPerson().get(0).getValue().getClass());
    assertEquals(Long.class, personDTO.getPerson().get(1).getValue().getClass());
}

5. 配置 ObjectMapper

上述方法适合特定字段的定制行为。但如果需要为整个类或多个类应用相同规则,可以配置 ObjectMapper 使用 USE_LONG_FOR_INTS 反序列化特性

PersonDTO readJsonWithLongForInts(String json) throws JsonProcessingException {
    ObjectMapper mapper = new ObjectMapper();
    mapper.enable(DeserializationFeature.USE_LONG_FOR_INTS);
    return mapper.readValue(json, PersonDTO.class);
}

启用 USE_LONG_FOR_INTS 特性强制Jackson将所有整数值反序列化为 Long 类型。

测试配置效果:

@Test
void givenJsonWithDifferentValueTypes_whenDeserializeWithLongForInts_thenLongValue() throws JsonProcessingException {
    String json = "{\"person\": [{\"key\": \"name\", \"value\": \"John\"}, {\"key\": \"id\", \"value\": 25}]}";
    PersonDTO personDTO = readJsonWithLongForInts(json);
    assertEquals(String.class, personDTO.getPerson().get(0).getValue().getClass());
    assertEquals(Long.class, personDTO.getPerson().get(1).getValue().getClass());
}

测试通过,JSON中的所有整数值都被反序列化为 Long 类型。

6. 使用 @JsonTypeInfo

前两种方法会将 value 字段的所有整数值转为 Long如果需要动态转换为特定类型,可以使用 @JsonTypeInfo 注解,但这要求输入JSON包含类型信息。

6.1. 在JSON中添加类型信息

修改JSON结构,为 value 字段添加类型信息:

{
  "person": [
    {
      "key": "name",
      "type": "string",
      "value": "John"
    },
    {
      "key": "id",
      "type": "long",
      "value": 25
    },
    {
      "key": "age",
      "type": "int",
      "value": 30
    }
  ]
}

添加 type 字段,并新增 Integer 类型的 age 字段以测试 intlong 值的反序列化。

6.2. 定制DTO

修改 KeyValuePair 类包含类型信息:

public class PersonDTOWithType {
    private List<KeyValuePair> person;

    public static class KeyValuePair {
        private String key;

        @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property = "type")
        @JsonSubTypes({
            @JsonSubTypes.Type(value = String.class, name = "string"),
            @JsonSubTypes.Type(value = Long.class, name = "long"),
            @JsonSubTypes.Type(value = Integer.class, name = "int")
        })
        private Object value;

        // 构造方法、getter和setter
    }
}

使用 @JsonTypeInfo 指定 value 字段的类型信息,声明名为 "type" 的外部属性包含类型信息。

使用 @JsonSubTypes 定义所有可能的子类型:当 type 字段值为 "string" 时转换为 String 对象。

Jackson反序列化时会根据类型信息确定值的精确类型。

6.3. 测试

编写测试验证行为:

@Test
void givenJsonWithDifferentValueTypes_whenDeserializeWithTypeInfo_thenSuccess() throws JsonProcessingException {
    String json = "{\"person\": [{\"key\": \"name\", \"type\": \"string\", \"value\": \"John\"}, {\"key\": \"id\", \"type\": \"long\", \"value\": 25}, {\"key\": \"age\", \"type\": \"int\", \"value\": 30}]}";
    PersonDTOWithType personDTO = readJsonWithValueType(json);
    assertEquals(String.class, personDTO.getPerson().get(0).getValue().getClass());
    assertEquals(Long.class, personDTO.getPerson().get(1).getValue().getClass());
    assertEquals(Integer.class, personDTO.getPerson().get(2).getValue().getClass());
}

测试通过,id 值被转换为 Longage 值被转换为 Integer

7. 总结

本文学习了强制Jackson反序列化为指定类型的多种方法:

  • ✅ 自定义反序列化器
  • ✅ 配置 ObjectMapper 的反序列化特性
  • ✅ 使用 @JsonTypeInfo 指定类型信息

每种方法适用场景不同,根据实际需求选择即可。完整代码示例可在 GitHub 获取。


原始标题:Forcing Jackson to Deserialize to Specific Type | Baeldung