Java Jackson @JsonMerge Annotation
1. @JsonMerge가 하는 일
가장 자주 사용되는 Jackson 기능 중 하나는 JSON을 Java 객체에 매핑하고 그 반대의 작업을 수행할 수 있는 ObjectMapper이다. ObjectMapper의 기능 중 하나는 JSON이 올바른 구조에 있다고 가정하고 객체를 읽고 JSON String의 새 데이터로 업데이트하는 것이다. @JsonMerge
가 도입되기 전에는 POJO 및 Map을 덮어쓰는 업데이트 기능의 제한이 있었다. 이 주석을 사용하면 중첩된 POJO 및 Map 내의 속성이 업데이트에 병합된다.
@JsonMerge를 실제로 사용하는 방법이다. 먼저 키보드라는 두 개의 개체를 만든다.
1
2
3
4
5
class Keyboard {
String style;
String layout;
// Standard getters, setters and constructors
}
둘째, 키보드를 사용할 프로그래머이다.
1
2
3
4
5
6
class ProgrammerNotAnnotated {
String name;
String favouriteLanguage;
Keyboard keyboard;
// Standard getters, setters and constructors
}
나중에 @JsonMerge
주석을 추가할 예정이다.
2. @JsonMerge 없이 병합
객체를 업데이트하려면 먼저 병합하려는 새 데이터를 나타내는 JSON 문자열이 필요하다.
1
String newData = "{\"favouriteLanguage\":\"Java\",\"keyboard\":{\"style\":\"Mechanical\"}}";
그런 다음 새 데이터로 업데이트하려는 객체를 생성해야 한다.
1
ProgrammerNotAnnotated programmerToUpdate = new ProgrammerNotAnnotated("John", "C++", new Keyboard("Membrane", "US"));
방금 정의한 문자열과 객체를 사용하여 주석 없이 어떤 일이 일어나는지 확인한다. 먼저 ObjectMapper 인스턴스를 생성하고 이를 사용하여 ObjectReader를 생성한다. ObjectReader는 더 적은 오버헤드로 ObjectMapper와 동일한 기능을 많이 사용할 수 있는 가볍고 스레드로부터 안전한 객체이다. ObjectReader 인스턴스는 만들고 구성하는데 비용이 매우 저렴하므로 직렬화/역직렬화별로 사용할 수 있다.
ObjectMapper.readerForUpdating()
을 사용하여 ObjectReader를 생성하고 업데이트하려는 개체를 유일한 인수로 전달한다. 이는 JSON String의 새 데이터로 지정된 객체를 업데이트하는 ObjectReader 인스턴스를 반환하기 위한 팩토리 메서드이다. ObjectReader가 있으면 간단히 readValue()
를 호출하고 새 데이터를 전달한다.
1
2
3
4
5
6
7
8
9
10
@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());
}
그런 다음 업데이트를 인쇄하여 최종 결과를 명확하게 확인할 수 있다.
1
{name='John', favouriteLanguage='Java', keyboard=Keyboard{style='Mechanical', layout='null'}}
programmerToUpdate가 최상위 업데이트를 받았다는 테스트 주장과 JSON을 보면 그가 가장 좋아하는 언어는 이제 Java이다. 그러나 중첩된 Keyboard 개체를 완전히 덮어썼으며 새 데이터에 스타일만 포함되어 있어도 레이아웃 속성이 손실되었다. 키보드와 같은 POJO를 병합하는 기능은 @JsonMerge
주석의 주요 이점 중 하나이다.
3. @JsonMerge로 병합
@JsonMerge
주석을 사용하여 새 Programmer 개체를 만든다.
1
2
3
4
5
6
7
class ProgrammerAnnotated {
String name;
String favouriteLanguage;
@JsonMerge
Keyboard keyboard;
// Standard getters, setters and constructors
}
해당 객체를 위와 같은 방식으로 사용하면 다른 결과를 얻는다.
1
2
3
4
5
6
7
8
9
10
11
12
@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를 업데이트했는지 확인할 수 있다.
1
{name='John', favouriteLanguage='Java', keyboard=Keyboard{style='Mechanical', layout='US'}}
주석의 동작은 여기에서 명확하게 볼 수 있다. 중첩된 개체의 수신 필드는 기존 필드를 덮어쓴다. 새 데이터에 일치하는 항목이 없는 필드는 그대로 유지된다.
4. @JsonMerge를 사용 하여 맵 병합
맵을 병합하는 과정은 우리가 이미 본 것과 매우 유사하다. 예제에서 사용할 수 있는 맵을 사용하여 객체를 생성한다.
1
2
3
4
5
6
class ObjectWithMap {
String name;
@JsonMerge
Map<String, String> stringPairs;
// Standard getters, setters and constructors
}
그런 다음 객체를 업데이트할 맵이 포함된 시작 JSON 문자열을 생성한다.
1
String newData = "{\"stringPairs\":{\"field1\":\"value1\",\"field2\":\"value2\"}}";
마지막으로 업데이트하려는 ObjectWithMap 인스턴스가 필요하다.
1
2
3
Map<String, String> map = new HashMap<>();
map.put("field3", "value3");
ObjectWithMap objectToUpdateWith = new ObjectWithMap("James", map);
이제 객체를 업데이트하기 위해 이전에 사용한 것과 동일한 프로세스를 사용할 수 있다.
1
2
3
4
5
6
7
8
9
@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"));
}
최종 결과를 확인하기 위해 업데이트를 다시 인쇄하면 다음과 같다.
1
{name='James', something={field1=value1, field3=value3, field2=value2}}
테스트와 결과물을 통해 주석을 사용하면 Map에 세 쌍이 모두 존재한다는 것을 알 수 있다. 주석이 없으면 새 데이터의 쌍만 갖게 된다.