1. 概述
本教程將探討 Jackson Java 庫中的 <em @JsonMerge</em> 註解。Jackson 以提供在 Java 應用程序中處理 JSON 的能力而聞名。此註解允許我們在嵌套的 POJO(純 Java 對象)或 <em Map</em> 中合併新的數據。我們將研究在不使用註解的情況下現有的功能,然後查看使用它在代碼中會產生什麼不同。
2. @JsonMerge 的作用
Jackson 中最常用的特性之一是 ObjectMapper,它允許我們將 JSON 映射到我們的 Java 對象,以及反之。 ObjectMapper 的一項能力是讀取一個對象並使用來自 JSON 字符串 的新數據更新它,前提是 JSON 的結構正確。 在引入 @JsonMerge 之前,這種更新能力的一個侷限性是它會覆蓋 POJO 和 Map。 通過此註解,嵌套 POJO 和 Map 中的屬性在更新時會被合併。
讓我們看看如何在實踐中使用 @JsonMerge。 我們將創建兩個對象,首先是鍵盤:
class Keyboard {
String style;
String layout;
// Standard getters, setters and constructors
}
其次,使用鍵盤的程序員:
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"));讓我們使用我們剛才定義的 String 和對象,看看在沒有註解的情況下會發生什麼。首先,我們創建一個 ObjectMapper 的實例,並使用它來創建一個 ObjectReader。 ObjectReader 是一個輕量級、線程安全的對象,我們可以用它來實現 ObjectMapper 的很多相同功能,但具有更少的開銷。由於 ObjectReader 實例非常廉價且易於創建和配置,因此我們可以按序列化/反序列化操作使用它們。
接下來,我們使用 ObjectMapper.readerForUpdating() 創建 ObjectReader,並將要更新的對象作為唯一的參數傳遞。這是一種工廠方法,專門用於返回一個 ObjectReader 實例,該實例將使用來自 JSON String 的新數據更新給定的對象。 獲得 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());
}之後,我們可以打印出 update 以查看最終結果是否清晰:
{name='John', favouriteLanguage='Java', keyboard=Keyboard{style='Mechanical', layout='null'}}我們可以從測試斷言和 JSON 數據中看出,programmerToUpdate 接收到了頂層更新,他的最愛語言現在是 Java。但是,我們完全覆蓋了嵌套的 Keyboard 對象,即使新數據只包含樣式,我們仍然丟失了佈局屬性。合併 POJO 這樣的能力,如 Keyboard,是 @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");
}最後,我們可以打印出 update,並確認我們已經更新了本次嵌套的 Keyboard POJO:
{name='John', favouriteLanguage='Java', keyboard=Keyboard{style='Mechanical', layout='US'}}標註的行為在這裏可以清楚地看到。嵌套對象中的傳入字段會覆蓋現有的字段。在新的數據中找不到匹配的字段將保持不變。
5. 合併Map 使用 @JsonMerge
合併Map的過程與我們之前所見的方法非常相似。讓我們創建一個包含Map的對象,以便演示:
class ObjectWithMap {
String name;
@JsonMerge
Map<String, String> stringPairs;
// Standard getters, setters and constructors
}隨後,我們創建一個起始的 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 對象中使用 <em @JsonMerge</em> 註解,我們可以讓 Jackson 合併嵌套的 POJO 和 <em Map</em> 對象。 在沒有使用該註解的情況下,Jackson 會覆蓋它們,因此它的實用性取決於我們的用例。