概述

在这个教程中,我们将探讨来自 Jackson Java库的@JsonMerge注解。Jackson因在Java应用中处理JSON而广受欢迎。这个注解允许我们在嵌套POJO(简单的Java对象)或Map中合并新的数据到一个对象。我们将首先了解不使用注解时的功能,然后看看在代码中使用它会带来什么差异。

2. @JsonMerge的作用

Jackson的一个常用特性是ObjectMapper,它能够将JSON映射到我们的Java对象,并实现反向操作。ObjectMapper的一个功能是读取一个对象并用来自JSON字符串的新数据更新它,前提是JSON结构正确。在引入@JsonMerge之前,这个更新能力的一个限制是会覆盖POJO和Map有了这个注解,嵌套POJO和Map中的属性在更新时会被合并。

3. 不使用@JsonMerge的合并

要更新对象,我们首先需要代表新数据的JSON字符串:

class Keyboard {
    String style;
    String layout;
    // Standard getters, setters and constructors
}

然后,我们需要创建一个包含新数据的对象:

class ProgrammerNotAnnotated {
    String name;
    String favouriteLanguage;
    Keyboard keyboard;
    // Standard getters, setters and constructors
}

现在,让我们使用定义的String和对象,不使用注解来看看会发生什么。首先,我们需要创建一个ObjectMapper实例,并使用它来创建一个ObjectReaderObjectReader是一个轻量级、线程安全的对象,可以像ObjectMapper一样提供很多功能,但开销更小。我们可以根据序列化/反序列化的需要为每个实例创建ObjectReader,因为它们非常便宜且易于配置。

我们使用ObjectMapper.readerForUpdating()方法创建ObjectReader,传入我们想要更新的对象作为参数。这是一个专门用于返回将给定对象与来自JSON字符串的新数据进行更新的ObjectReader实例的方法。有了ObjectReader后,我们只需调用readValue()并传入我们的新数据:

String newData = "{\"favouriteLanguage\":\"Java\",\"keyboard\":{\"style\":\"Mechanical\"}}";

之后,我们可以打印update以清楚地看到结果:

ProgrammerNotAnnotated programmerToUpdate = new ProgrammerNotAnnotated("John", "C++", new Keyboard("Membrane", "US"));

从测试断言和JSON中可以看出,我们的programmerToUpdate接收到了顶级更新,他最喜欢的编程语言现在是Java。然而,我们完全覆盖了嵌套的Keyboard对象,尽管新数据只包含了样式,但我们丢失了布局属性。@JsonMerge注解对于像Keyboard这样的POJO的合并能力是主要优点,我们将在下一节中看到这一点。

4. 使用@JsonMerge的合并

现在,让我们创建一个带有@JsonMerge注解的新Programmer对象:

class ProgrammerAnnotated {
    String name;
    String favouriteLanguage;
    @JsonMerge
    Keyboard keyboard;
    // Standard getters, setters and constructors
}

如果我们以相同的方式使用这个对象,我们会得到不同的结果:

@Test
void givenAnObjectAndJson_whenUsingJsonMerge_thenExpectUpdateInPOJO() throws JsonProcessingException {
    String newData = "{\"favouriteLanguage\":\"Java\",\"keyboard\":{\"style\":\"Mechanical\"}}";
    ProgrammerAnnotated programmerToUpdate = new ProgrammerAnnotated("John", "C++", new Keyboard("Membrane", "US"));

    ObjectMapper objectMapper = new ObjectMapper();
    ProgrammerAnnotated update = objectMapper.readerForUpdating(programmerToUpdate).readValue(newData);

    assert(update.getFavouriteLanguage()).equals("Java");
    // Only works with annotation
    assert(update.getKeyboard().getLayout()).equals("US");
}

最后,我们可以打印update,看到这次我们已经更新了嵌套的Keyboard POJO:

{name='John', favouriteLanguage='Java', keyboard=Keyboard{style='Mechanical', layout='US'}}

这里清楚地展示了注解的行为。新数据中的嵌套对象字段会覆盖现有字段。新数据中没有匹配的字段将保持不变。

5. 使用@JsonMerge合并Map

合并Map的过程与我们之前看到的类似。让我们创建一个可以用来演示的Map对象:

class ObjectWithMap {
    String name;
    @JsonMerge
    Map<String, String> stringPairs;
    // Standard getters, setters and constructors
}

接下来,创建一个包含我们将用来更新对象的Map的起始JSON字符串:

String newData = "{\"stringPairs\":{\"field1\":\"value1\",\"field2\":\"value2\"}}";

最后,我们需要我们想要更新的ObjectWithMap实例:

Map<String, String> map = new HashMap<>();
map.put("field3", "value3");
ObjectWithMap objectToUpdateWith = new ObjectWithMap("James", map);

现在,我们可以使用之前的相同过程来更新对象:

@Test
void givenAnObjectWithAMap_whenUsingJsonMerge_thenExpectAllFieldsInMap() throws JsonProcessingException {
    ObjectMapper objectMapper = new ObjectMapper();
    ObjectWithMap update = objectMapper.readerForUpdating(objectToUpdateWith).readValue(newData);

    assertTrue(update.getStringPairs().containsKey("field1"));
    assertTrue(update.getStringPairs().containsKey("field2"));
    assertTrue(update.getStringPairs().containsKey("field3"));
}

再次打印update以查看最终结果,看起来如下:

{name='James', something={field1=value1, field3=value3, field2=value2}}

从测试和打印输出中可以看出,使用注解的结果是Map中存在所有三对元素。如果没有注解,我们只会得到新数据中的键值对。

6. 结论

在这篇文章中,我们了解到我们可以使用Jackson将新的JSON数据更新到现有对象中。此外,通过在Java对象上使用@JsonMerge注解,我们可以让Jackson合并嵌套的POJO和Map。没有注解时,Jackson会覆盖它们,所以它的实用性取决于我们的具体应用场景。

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