1. 概述

在这个教程中,我们将探讨将Jackson的原始数据类型JsonNode转换为Java集合的不同方法。虽然我们可以直接使用JsonNode读取JSON,但将其转换为Java集合是有益的。Java集合提供了类型安全、更快的处理速度以及更多针对特定类型的操作优势。

2. 示例设置

在我们的代码示例中,我们将查看将JsonNode转换为对象列表或映射的各种方式。让我们先构建示例的基础结构。

2.1. 依赖项

首先,我们在pom.xml文件中添加Jackson核心库的依赖:

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

2.2. JSON数据

接下来,我们定义一个用于我们示例的JSON:

{
    "persons": [
        {
            "name": "John",
            "age": 30
        },
        {
            "name": "Alice",
            "age": 25
        }
    ],
    "idToPerson" : {
        "1234": {
            "name": "John",
            "age": 30
        },
        "1235": {
            "name": "Alice",
            "age": 25
        }
    }
}

在这个JSON中,我们有一个名为persons的JSON数组和一个名为idToPerson的JSON对象。我们将探讨如何将它们转换为Java集合。

2.3. DTO(数据传输对象)

现在,我们定义一个名为Person的类,它将在示例中使用:

public class Person {
    private String name;
    private int age;
    
    // constructors/getters/setters
}

2.4. 将JSON字符串转换为JsonNode

如果我们要读取整个JSON对象,可以使用Jackson的ObjectMapper类:

JsonNode rootNode = new ObjectMapper().readTree(jsonString);
JsonNode childNode = rootNode.get("persons");

*要将整个JSON转换为JsonNode对象,我们可以使用readTree()方法。然后,我们使用get()方法遍历JsonNode对象,获取指定名称的嵌套对象。*

3. 手动转换

在查看库方法之前,让我们先看看如何手动将JsonNode转换为集合。

3.1. 手动将JsonNode转换为列表

要将JsonNode转换为列表,我们可以逐条遍历并创建一个包含它的列表:

List<Person> manualJsonNodeToList(JsonNode personsNode) {
    List<Person> people = new ArrayList<>();
    for (JsonNode node : personsNode) {
        Person person = new Person(node.get("name").asText(), node.get("age").asInt());
        people.add(person);
    }

    return people;
}

这里,我们使用循环遍历输入节点的所有子节点。这只有在输入节点是数组时才可行。对于每个节点,我们创建一个Person对象,并将其添加到列表中。我们使用get(fieldName)方法从节点获取nameageJsonNode提供了各种方法将返回值转换为基本的Java类型。在这里,asText()asInt()方法分别将值转换为Stringint

3.2. 手动将JsonNode转换为映射

接下来,让我们看看映射的类似转换:

Map<String, Person> manualJsonNodeToMap(JsonNode idToPersonsNode) {
    Map<String, Person> mapOfIdToPerson = new HashMap<>();
    idToPersonsNode.fields()
      .forEachRemaining(node -> mapOfIdToPerson.put(node.getKey(),
        new Person(node.getValue().get("name").asText(), node.getValue().get("age").asInt())));

    return mapOfIdToPerson;
}

这里,我们使用fields()方法迭代映射条目。它返回一个Iterator<Map.Entry<String, JsonNode>>对象,我们可以进一步处理。然后,我们读取每个条目并将它放入我们的映射中。

4. 使用Jackson的readValue()convertValue()

Jackson提供了多种方法将JsonNode转换为Java对象。让我们看看其中的两个。

4.1. 使用readValue()

可以使用readValue()方法,通过TypeReferenceJsonNode转换为列表或映射:

List<Person> readValueJsonNodeToList(JsonNode personsNode) throws IOException {
    TypeReference<List<Person>> typeReferenceList = new TypeReference<List<Person>>() {};
    return new ObjectMapper().readValue(personsNode.traverse(), typeReferenceList);
}

Map<String, Person> readValueJsonNodeToMap(JsonNode idToPersonsNode) throws IOException {
    TypeReference<Map<String, Person>> typeReferenceMap = new TypeReference<Map<String, Person>>() {};
    return new ObjectMapper().readValue(idToPersonsNode.traverse(), typeReferenceMap);
}

首先,我们创建一个TypeReference对象,传入我们需要转换的确切类型。然后,我们调用readValue()方法,其中JsonParserjsonNode.traverse()提供。使用解析器,根据提供的TypeReference,它将节点解串化为列表或映射。

4.2. 使用convertValue()

同样,我们可以使用*convertValue()*方法:

List<Person> convertValueJsonNodeToList(JsonNode personsNode) {
    TypeReference<List<Person>> typeReferenceList = new TypeReference<List<Person>>() {};
    return new ObjectMapper().convertValue(personsNode, typeReferenceList);
}

Map<String, Person> convertValueJsonNodeToMap(JsonNode idToPersonsNode) {
    TypeReference<Map<String, Person>> typeReferenceMap = new TypeReference<Map<String, Person>>() {};
    return new ObjectMapper().convertValue(idToPersonsNode, typeReferenceMap);
}

convertValue()方法的工作原理是先序列化输入对象,然后将其反序列化为目标类型。因此,它更灵活,可用于从一个对象到另一个对象的转换。例如,我们也可以用它将Java对象反向转换为JsonNode

5. 定制反序列化器

我们还可以提供一个自定义反序列化器来执行转换。让我们看看如何定义一个:

public class CustomPersonListDeserializer extends JsonDeserializer<List<Person>> {
    @Override
    public List<Person> deserialize(com.fasterxml.jackson.core.JsonParser p, 
      com.fasterxml.jackson.databind.DeserializationContext ctxt) throws IOException {
        ObjectMapper objectMapper = (ObjectMapper) p.getCodec();
        List<Person> personList = new ArrayList<>();
        JsonNode personsNode = objectMapper.readTree(p);
        for (JsonNode node : personsNode) {
            personList.add(objectMapper.readValue(node.traverse(), Person.class));
        }

        return personList;
    }
}

让我们看看代码中的一些关键部分:

  • 首先,类继承了Jackson的JsonDeserializer
  • 然后,我们重写deserialize()方法并提供实现。
  • 在实现中,我们从JsonParser对象获取ObjectMapper
  • objectMapper.readTree()将解析器表示的整个树转换为一个JsonNode实例。
  • 最后,类似于手动转换,我们通过遍历JSON数组中的每个节点,将每个节点转换为一个Person对象。

反序列化器的工作方式与其他方法相似,但提供了一种职责分离。此外,自定义反序列化器提供了灵活性,因为我们在调用代码中可以轻松切换不同的反序列化器。

在下一节中,我们将看到如何在测试代码时使用这个反序列化器。

6. 测试

现在我们的不同方法都准备好了,让我们编写一些测试来验证它们。

6.1. 设置

让我们设置测试类:

public class JsonNodeToCollectionUnitTest {

    public static String jsonString = "{\"persons\":[{\"name\":\"John\",\"age\":30},{\"name\":\"Alice\",\"age\":25}],\"idToPerson\":{\"1234\":{\"name\":\"John\",\"age\":30},\"1235\":{\"name\":\"Alice\",\"age\":25}}}";

    static JsonNode completeJson;
    static JsonNode personsNode;
    static JsonNode idToPersonNode;

    @BeforeAll
    static void setup() throws JsonProcessingException {
        completeJson = new ObjectMapper().readTree(jsonString);
        personsNode = completeJson.get("persons");
        idToPersonNode = completeJson.get("idToPerson");
    }
}

这里,我们定义了一个JSON字符串,作为测试输入。然后,我们定义了一个setup()方法,在所有测试之前执行。它设置了我们的输入JsonNode对象。

6.2. 测试转换方法

接下来,让我们测试转换方法:

@Test
void givenJsonNode_whenConvertingToList_thenFieldsAreCorrect() throws IOException {

    List<Person> personList1 = JsonNodeConversionUtil.manualJsonNodeToList(personsNode);
    List<Person> personList2 = JsonNodeConversionUtil.readValueJsonNodeToList(personsNode);
    List<Person> personList3 = JsonNodeConversionUtil.convertValueJsonNodeToList(personsNode);

    validateList(personList1);
    validateList(personList2);
    validateList(personList3);
}

private void validateList(List<Person> personList) {
    assertEquals(2, personList.size());

    Person person1 = personList.get(0);
    assertEquals("John", person1.getName());
    assertEquals(30, person1.getAge());

    Person person2 = personList.get(1);
    assertEquals("Alice", person2.getName());
    assertEquals(25, person2.getAge());
}

这里,我们调用每个方法并验证返回列表的内容符合预期。

我们为映射转换添加一个类似的测试:

@Test
void givenJsonNode_whenConvertingToMap_thenFieldsAreCorrect() throws IOException {

    Map<String, Person> personMap1 = JsonNodeConversionUtil.manualJsonNodeToMap(idToPersonNode);
    Map<String, Person> personMap2 = JsonNodeConversionUtil.readValueJsonNodeToMap(idToPersonNode);
    Map<String, Person> personMap3 = JsonNodeConversionUtil.convertValueJsonNodeToMap(idToPersonNode);

    validateMapOfPersons(personMap1);
    validateMapOfPersons(personMap2);
    validateMapOfPersons(personMap3);
}

private void validateMapOfPersons(Map<String, Person> map) {
    assertEquals(2, map.size());

    Person person1 = map.get("1234");
    assertEquals("John", person1.getName());
    assertEquals(30, person1.getAge());

    Person person2 = map.get("1235");
    assertEquals("Alice", person2.getName());
    assertEquals(25, person2.getAge());
}

6.3. 测试自定义反序列化器

现在来看看如何使用自定义反序列化器,然后比较结果:

@Test
void givenJsonNode_whenConvertingToListWithCustomDeserializer_thenFieldsAreCorrect() throws IOException {
    ObjectMapper objectMapper = new ObjectMapper();
    SimpleModule module = new SimpleModule();
    module.addDeserializer(List.class, new CustomPersonListDeserializer());
    objectMapper.registerModule(module);
    List<Person> personList = objectMapper.convertValue(personsNode, new TypeReference<List<Person>>() {
    });

    validateList(personList);
}

在这里,我们首先使用SimpleModule类将CustomPersonListDeserializer包装在一个模块中。然后,我们使用该模块注册到ObjectMapper实例中。这样,我们就指示ObjectMapper在需要解序列化列表时使用CustomPersonListDeserializer

当我们接下来调用convertValue()方法时,将使用反序列化器返回输出列表。

7. 结论

在这篇文章中,我们探讨了如何将Jackson的JsonNode转换为像列表和映射这样的类型化的Java集合。我们讨论了执行此操作的不同方法,并测试了它们都能得到相同的结果。

对于小规模转换,我们可能使用手动方法。随着JSON大小的增长,使用Jackson库的方法会更好。如果需要更复杂的转换,提供自定义反序列化器会更合适。

如往常一样,示例代码的源代码可以在GitHub上找到