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
}
第二,将使用该 Keyboard
的 Programmer
:
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
的实例,然后用它来创建一个 ObjectReader
。ObjectReader
是一个轻量级的、线程安全的对象,我们可以用它来实现很多与 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会覆盖它们,所以它的用处取决于我们的实际需求。