Jackson 中的 @JsonMerge 注解

1, 概览

在本教程中,我们将看一下Jackson Java库中的 @JsonMerge 注解。Jackson 以提供在我们的Java应用程序中处理JSON的能力而闻名。这个注解允许我们将新数据合并到嵌套的POJO(普通的Java对象)或 Map 中的一个对象中。我们将看看没有注解的现有功能,然后看看当我们在代码中使用它时有什么不同。

2, @JsonMerge的作用

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

让我们看看如何在实践中使用 @JsonMerge。我们将创建两个对象,首先是一个 Keyboard:

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

第二,将使用该 KeyboardProgrammer

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

稍后,我们将添加 @JsonMerge 注解,但现在,我们已经准备好了。

3,在没有 @JsonMerge 的情况下进行合并

要更新一个对象,我们首先需要JSON字符串来表示我们要合并的新数据:

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

然后我们需要创建我们要用新数据更新的对象:

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

让我们使用我们刚刚定义的字符串和对象,看看没有注解会发生什么。我们将首先创建一个 ObjectMapper 的实例,然后用它来创建一个 ObjectReaderObjectReader 是一个轻量级的、线程安全的对象,我们可以用它来实现很多与 ObjectMapper 相同的功能,但开销更少。我们可以在每个序列化/反序列化的基础上使用 ObjectReader 实例,因为它们的创建和配置都非常轻量。

我们将用 ObjectMapper.readerForUpdating() 创建 ObjectReader,把我们要更新的对象作为唯一参数传入。这是一个工厂方法,专门用于返回一个 ObjectReader 实例,它将用JSON字符串的新数据更新给定的对象。一旦我们有了 ObjectReader,我们只需调用 readValue() 并传入我们的新数据:

@Test
void givenAnObjectAndJson_whenNotUsingJsonMerge_thenExpectNoUpdateInPOJO() throws JsonProcessingException {
    ObjectMapper objectMapper = new ObjectMapper();
    ObjectReader objectReader = objectMapper.readerForUpdating(programmerToUpdate);
    ProgrammerNotAnnotated update = objectReader.readValue(newData);

    assert(update.getFavouriteLanguage()).equals("Java");
    assertNull(update.getKeyboard()
      .getLayout());
}

之后,我们可以打印出更新,清楚地看到我们最终的结果:

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

我们可以从测试断言和JSON中看到,我们的 ProgrammerToUpdate 收到了顶级的更新,他最喜欢的语言现在是 Java。然而,我们已经完全覆盖了嵌套的 Keyboard 对象,尽管新的数据只包含一个 style,但我们已经失去了 layout 属性。合并像 Keyboard 这样的POJO的能力是 @JsonMerge 注解的主要好处之一,我们将在下一节看到。

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");
}

最后,我们可以打印出更新,看到我们这次已经更新了嵌套的 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
}

在这之后,让我们创建一个初始的JSON字符串,其中包含一个我们要更新对象的 Map:

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"));
}

如果我们再次打印出更新,以获得最终结果的视图,它看起来像这样:

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

我们从测试和打印结果中看到,使用注解的结果是 Map 中存在所有的三个键值对。如果没有注解,我们将只有来自新数据的键值对。

6,总结

在这篇文章中,我们已经看到,我们可以使用Jackson用新传入的JSON数据来更新一个现有的对象。此外,通过在我们的Java对象中使用 @JsonMerge 注解,我们可以让Jackson来合并嵌套的POJO和Map。如果没有这个注解,Jackson会覆盖它们,所以它的用处取决于我们的实际需求。

原文: @JsonMerge Annotation in Jackson | Baeldung