概述
在这个教程中,我们将探讨来自 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
实例,并使用它来创建一个ObjectReader
。ObjectReader
是一个轻量级、线程安全的对象,可以像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上找到。