- ๋ชฉ์ฐจ
- Jackson ์ดํดํ๊ธฐ - ์ฌ์ฉ ๋ฐฉ๋ฒ๊ณผ ๋์ ๋ฐฉ์
- Jackson ์์กด์ฑ
- 1 ObjectMapper๋ฅผ ์ด์ฉํ ์ง๋ ฌํ, ์ญ์ง๋ ฌํ
- JsonNode๋ฅผ ์ด์ฉํ ์ง๋ ฌํ, ์ญ์ง๋ ฌํ
- Jackson ์ง๋ ฌํ ์ญ์ง๋ ฌํ ๋์ ๋ฐฉ์ - JsonParser, JsonGenerator
- ์ฐธ๊ณ
API ์ ํ๋ฆฌ์ผ์ด์ ์๋ฒ๋ฅผ ๋ง๋ค ๋ ๋น ์ง ์ ์๋ ๋ถ๋ถ์ด ๊ฐ์ฒด์ JSON๊ฐ์ ๋ณํ์ด๋ค.
์ด๋ฅผ ๋ณดํต ์ง๋ ฌํ (๊ฐ์ฒด -> JSON), ์ญ์ง๋ ฌํ (JSON -> ๊ฐ์ฒด)๋ผ๊ณ ๋ถ๋ฅธ๋ค.
์ด๋ฒ ๊ธ์ Java๋ด ๋ณดํธ์ ์ผ๋ก ๋ง์ด ์ฌ์ฉ๋๋ JSON ๋ผ์ด๋ธ๋ฌ๋ฆฌ Jackson์ ๋์ ๋ฐฉ์๊ณผ ์ฌ์ฉ ๋ฐฉ๋ฒ์ ๋ํด์ ์์๋ณธ๋ค.
์ฌ์ฉ ๋ฐฉ๋ฒ๋ง๊ณ ๋์ ๋ฐฉ์์ ์๊ณ ์ถ์ผ์ ๋ ์๋ผ๋ฉด ๋ฐ๋ก ๋ถ๋ถ๋ถํฐ ๋ณด๋ ๊ฒ์ ์ถ์ฒํ๋ค.
Jackson has been known as "the Java JSON library" or "the best JSON parser for Java". Or simply as "JSON for Java".
Jackson ๊ณต์ ์ ์ฅ์์ ์ ํ์๋ ๋ฌธ๊ตฌ๋ก Jackson์ ์ด๋ฆ์ ๊ฐ๋จํ "JSON for Java"์ ์๋ฏธ๋ผ๊ณ ํ๋ค.
Jackson์ ์ฌ์ฉํ๊ณ ์ํ๋ฉด jackson์ databind ์์กด์ฑ์ ์ถ๊ฐํด์ฃผ๋ฉด๋๋ค.
implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.3'
databind ์์กด์ฑ์ ์ถ๊ฐํ๋ฉด ์๋์ผ๋ก ๋ค์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ์ ์์ ์ผ๋ก ์ถ๊ฐ๋๋ค.
- jackson-annotations
- jackson-core
์ต์ databind ์์กด์ฑ์ Maven Repository๋ฅผ ์ฐธ๊ณ ํ๋ฉด ๋๋ค.
ObjectMapper
Jackson Databind ๋ชจ๋์ ์์นํ ๊ฐ์ฒด๋ก์, POJO ๋๋ JSON ํธ๋ฆฌ ๋ชจ๋ธ (JsonNode
)๋ฅผ JSON๋ก ์ฝ๊ณ ์ฐ๊ณ ๋ณํํ๋ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ค.
๋ํ, ์ฌ์ฉ์๊ฐ ์ง์ ์ง๋ ฌํ, ์ญ์ง๋ ฌํ์ ๋ํ ์ปค์คํ ์ ํ ์ ์๊ฒ ๋์์ค๋ค.
ObjectMapper
์ ์ฌ์ฉ ๋ฐฉ๋ฒ์ ์ดํดํ๊ธฐ์ํด ์๋์ ๊ฐ์ด ๊ฐ๋จํ POJO๋ฅผ ์์ฑํด์ค๋ค.
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Setter
@Getter
public class Member {
private String id;
private String name;
private int age;
}
๊ฐ์ฅ ๋จผ์ ObjectMapper๋ฅผ ์ด์ฉํ ์ง๋ ฌํ์ ๋ํด์ ์ดํด๋ณธ๋ค.
ObjectMapper๋ฅผ ํ์ฉํ ๊ฐ์ฒด์์ JSON์ผ๋ก ์ง๋ ฌํํ๋ ๋ฉ์๋๋ 3๊ฐ์ง ์กด์ฌํ๋ค.
- writeValue()
- writeValueAsString()
- writeValueAsBytes()
writeValue() ์์
@Test
void object_to_json() throws IOException {
// given
ObjectMapper objectMapper = new ObjectMapper();
// when, then
Member member = Member.builder()
.id("123")
.name("binghe")
.age(28)
.build();
objectMapper.writeValue(new FileOutputStream("data/member.json"), member);
}
์์ ๊ฐ์ด writeValue๋ฅผ ํ์ฉํด File, OutputStream, Writer๋ฑ ๋ค์ํ ๋ฐฉ์์ผ๋ก ์ง๋ ฌํ ํ ์ ์๋ค.
๋ณดํต API ์๋ฒ์์ writeValueAsString()
์ ๊ฐ์ฅ ๋ง์ด ์ฌ์ฉํ๋ค.
writeValueAsString() ์์
@Test
void object_to_json_String() throws JsonProcessingException {
// given
ObjectMapper objectMapper = new ObjectMapper();
// when
Member member = Member.builder()
.id("123")
.name("binghe")
.age(28)
.build();
String json = objectMapper.writeValueAsString(member);
// then
assertThat(json).isEqualTo("{\"id\":\"123\",\"name\":\"binghe\",\"age\":28}");
}
๋๋๋ก Jackson์์ ์ ๊ณตํ๋ ๋ํดํธ Serializer ๋ง๊ณ , ์ํฉ์ ๋ฐ๋ผ ์ปค์คํ ํ๊ฒ ๊ฐ์ฒด๋ฅผ JSON์ผ๋ก ์ง๋ ฌํํด์ผํ๋ ๊ฒฝ์ฐ๊ฐ ์๋ค.
์๋ฅผ ๋ค์ด JSON์์ Java ๊ฐ์ฒด์ ๋ค๋ฅธ ํ๋ ์ด๋ฆ์ ์ฌ์ฉํ๊ฑฐ๋ ํน์ ํ๋๋ฅผ ์์ ํ ์๋ตํด์ผํ๋ ๊ฒฝ์ฐ๊ฐ ์๋ค.
์๋ ์์๋ ๊ฐ์ฒด๋ด ํ๋ ์ด๋ฆ์ JSON์ผ๋ก ์ง๋ ฌํ์ ๋ณ๊ฒฝํด์ฃผ๋ ์ปค์คํ Serializer๋ฅผ ์ ์ฉํ ์์์ด๋ค.
@Test
void object_to_json_custom_serializer() throws JsonProcessingException {
// given
ObjectMapper objectMapper = new ObjectMapper();
MemberCustomSerializer memberCustomSerializer = new MemberCustomSerializer();
SimpleModule module = new SimpleModule("MemberCustomSerializer");
module.addSerializer(Member.class, memberCustomSerializer);
objectMapper.registerModule(module);
// when
Member mem = Member.builder()
.id("123")
.name("binghe")
.age(28)
.build();
String json = objectMapper.writeValueAsString(mem);
// then
assertThat(json).isEqualTo("{\"memberId\":\"123\",\"nickName\":\"binghe\",\"age\":28}");
}
private static class MemberCustomSerializer extends JsonSerializer<Member> {
@Override
public void serialize(Member value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeStartObject(); // {
gen.writeStringField("memberId", value.getId());
gen.writeStringField("nickName", value.getName());
gen.writeNumberField("age", value.getAge());
gen.writeEndObject(); // }
}
}
Module
์ปค์คํ
Serializer๋ฅผ Module์ ๋ฃ๊ณ , Module์ ObjectMapper
์ ๋ฑ๋กํด์ค์ผ๋ก์จ ์ปค์คํ
Serializer๋ฅผ ์ ์ฉํ ์ ์์๋ค.
์ด๋ ๊ฒ ObjectMapper์ Module๋ฅผ ๋ฑ๋กํจ์ผ๋ก์จ ๋ค์ํ ๋ฐฉ์์ผ๋ก ์ปค์คํ
ํ ์ ์์ผ๋ฉฐ, ObjectMapper
๋ ์ฌ๋ฌ ๊ฐ์ module
์ ๊ฐ์ง ์ ์๋ค.
- ObjectMapper 1 : N Module. (์ ํํ๋ Module์ ๋ค์ํ ObjectMapper์ ๋ฑ๋กํ ์ ์์ด N : N ๊ด๊ณ๋ผ๊ณ ํ ์ ์๋ค.)
- Module 1 : N Serializer (๋์ผํ๊ฒ ์ ํํ๋ N : N ๊ด๊ณ๋ผ๋ ๋ด๋ ๋ฌด๋ฐฉํ๋ค.)
Jackson์ ๋ค์ํ ์ ๋ ธํ ์ด์ ์ ์ ๊ณตํ๋ค.
์์ ๊ฐ์ด ์ปค์คํ Serializer๋ฅผ ๋ง๋ค์ด์ ์ฌ์ฉํด๋ ๋์ง๋ง, ์ฌ์ค Jackson์์ ๋ค์ํ ์ ๋ ธํ ์ด์ ์ ์ ๊ณตํ๋ค.
๊ฐ๋ฅํ ์ด๋ฌํ ์ ๋ ธํ ์ด์ ์ ํ์ฉํ๋ ๊ฒ์ด ์ถํ ์ ์ง๋ณด์๋ ํ์ฅ๋ฉด์์ ์ฉ์ดํ๋ฏ๋ก ํ์๋ ์ ๋ ธํ ์ด์ ์ ํ์ฉ์ ์งํฅํ๋ค.
์ด์ ObjectMapper๋ฅผ ์ด์ฉํ ์ญ์ง๋ ฌํ์ ๋ํด์ ์ดํด๋ณธ๋ค.
์ญ์ง๋ ฌํ์ readValue()
๋ฉ์๋๋ฅผ ์ฌ์ฉํ๋ฉด ๋๋ค.
@Test
void json_to_object() throws JsonProcessingException {
// given
ObjectMapper objectMapper = new ObjectMapper();
String json = "{\"id\":\"123\",\"name\":\"binghe\",\"age\":28}";
// when
Member member = objectMapper.readValue(json, Member.class);
// then
assertThat(member.getId()).isEqualTo("123");
assertThat(member.getName()).isEqualTo("binghe");
assertThat(member.getAge()).isEqualTo(28);
}
readValue()
์ ์ฒซ ๋ฒ์งธ ๋งค๊ฐ๋ณ์๋ก JSON (๋ฌธ์์ด, ๋๋ ์คํธ๋ฆผ, ํ์ผ)์ ์์ค๋ฅผ ๋ฃ๊ณ , ๋ ๋ฒ์งธ ๋งค๊ฐ๋ณ์๋ก ์ญ์ง๋ ฌํํ ๊ฐ์ฒด์ ํด๋์ค๋ฅผ ๋ฃ์ด์ฃผ๋ฉด ๋๋ค.
๋ฌธ์์ด์ธ JSON๋ฅผ ๊ฐ์ฒด๋ก ๋ณํ (์ญ์ง๋ ฌํ)ํ๊ธฐ ์ํด์ JSON ํ๋์ POJO์ ํ๋๋ฅผ ๋งคํ์์ผ์ผํ๋ค.
์ฆ, JSON๋ด name
ํ๋์ Member
๊ฐ์ฒด๋ด name
ํ๋๋ฅผ ๋งคํ์์ผ์ผํ๋ค.
Jackson์ ๊ธฐ๋ณธ์ ์ผ๋ก ๊ฐ์ฒด์ getter, setter ๋ฉ์๋ ์ด๋ฆ์ ํตํด JSON ํ๋์ POJO ํ๋๋ฅผ ๋งคํ์ํจ๋ค.
Jackson์ getter ๋ฐ setter ๋ฉ์๋ ์ด๋ฆ์ "get" ๋ฐ "set" ๋ถ๋ถ์ ์ ๊ฑฐํ๊ณ ๋๋จธ์ง ์ด๋ฆ์ ์ฒซ ๋ฒ์งธ ๋ฌธ์๋ฅผ ์๋ฌธ์๋ก ๋ณํํ ์ด๋ฆ๊ณผ JSON ํ๋๋ฅผ ๋งคํ์์ผ ์ญ์ง๋ ฌํํ๋ค.
์๋ฅผ ๋ค์ด, name
์ด๋ผ๋ JSON ํ๋๋ POJO๋ด getName()
ํน์ setName()
๋ฉ์๋๋ฅผ ์ฐพ์ "get", "set" ๋ถ๋ถ์ ์ ๊ฑฐํ๊ณ Name
์ name
์ผ๋ก ๋ณํํ์ฌ JSON ํ๋์ ๋งคํ์ํจ๋ค.
๋ง์ฝ ๋ค๋ฅธ ๋ฐฉ์์ผ๋ก ๋งคํ์ํค๊ณ ์ถ๋ค๋ฉด, ์ปค์คํ Deserializer๋ฅผ ๊ตฌํํ์ฌ ObjectMapper์ ์ถ๊ฐํด์ฃผ๋ฉด๋๋ฉฐ, ๋ค์ํ Jackson ์ ๋ ธํ ์ด์ ์ ํตํด์๋ ์ปค์คํ ํ ์ ์๋ค.
ObjectMapper
๋ JSON ๋ฐฐ์ด ๋ฌธ์์ด์์ ๊ฐ์ฒด ๋ฐฐ์ด๋ก ์ญ์ง๋ ฌํํ ์๋ ์๋ค.
๋ค์์ JSON ๋ฐฐ์ด ๋ฌธ์์ด์์ ๊ฐ์ฒด ๋ฐฐ์ด์ ์ฝ๋ ์์๋ค.
@Test
void json_array_to_objects() throws JsonProcessingException {
// given
ObjectMapper objectMapper = new ObjectMapper();
String json = "[{\"id\":\"123\",\"name\":\"binghe\",\"age\":28},{\"id\":\"456\",\"name\":\"binghe2\",\"age\":29}]";
// when
Member[] members = objectMapper.readValue(json, Member[].class);
// then
assertThat(members.length).isEqualTo(2);
assertThat(members[0].getId()).isEqualTo("123");
assertThat(members[0].getName()).isEqualTo("binghe");
assertThat(members[0].getAge()).isEqualTo(28);
assertThat(members[1].getId()).isEqualTo("456");
assertThat(members[1].getName()).isEqualTo("binghe2");
assertThat(members[1].getAge()).isEqualTo(29);
}
๋ฐฐ์ด๊ณผ ๋์ผํ๊ฒ ๋ฆฌ์คํธํ์ ๋ ์ญ์ง๋ ฌํํ ์ ์๋ค.
@Test
void json_list_to_objects() throws JsonProcessingException {
// given
ObjectMapper objectMapper = new ObjectMapper();
String json = "[{\"id\":\"123\",\"name\":\"binghe\",\"age\":28},{\"id\":\"456\",\"name\":\"binghe2\",\"age\":29}]";
// when
List<Member> members = objectMapper.readValue(json, new TypeReference<List<Member>>() {});
// then
assertThat(members.size()).isEqualTo(2);
assertThat(members.get(0).getId()).isEqualTo("123");
assertThat(members.get(0).getName()).isEqualTo("binghe");
assertThat(members.get(0).getAge()).isEqualTo(28);
assertThat(members.get(1).getId()).isEqualTo("456");
assertThat(members.get(1).getName()).isEqualTo("binghe2");
assertThat(members.get(1).getAge()).isEqualTo(29);
}
์ ์์์์ ์ค์ํ ์ ์ readValue()
์ ๋ ๋ฒ์งธ ๋งค๊ฐ๋ณ์๋ก TypeReference
๋ฅผ ๋๊ฒจ์ค๋ค๋ ์ ์ด๋ค.
์ด๋ TypeReference
๋ฅผ ์ฌ์ฉํ์ง์๊ณ List<Member>.class
๋ฅผ ๋๊ฒจ์ฃผ๋ฉด ์๋์ ๊ฐ์ ์๋ฌ๊ฐ ๋ฐ์ํ๋ค.
์์ ๊ฐ์ ์๋ฌ๊ฐ ๋ฐ์ํ๋ ์ด์ ๋ฅผ ์ดํดํ๊ธฐ์ํด์ ์๋ฐ์ ์ ๋ค๋ฆญ์ ๋จผ์ ์ดํดํด์ผํ๋ค.
์๋ฐ๋ ๊ธฐ๋ณธ์ ์ผ๋ก ์ ๋ค๋ฆญ์ผ๋ก ๋๊ฒจ์ง ๊ฐ์ฒด ํ์
์ ๋ฐํ์์ ์ฌ๋ผ์ง๋ค. ๊ทธ๋ฌ๋ฏ๋ก List.class
ํน์ List<Member>.class
๋ฅผ ๋๊ธด๋ค๊ณ ํด๋ ์๋ฐ ๋ฐํ์์ List.class
๋ก ํ์
์ด ์๋ฉธ๋๋ค.
๋ฌธ์ ๋ Jackson์ด JSON์ Member
๊ฐ์ฒด๋ก ๋ณํํ๊ณ ์ํ ๋, ์ด๋ค ๊ฐ์ฒด ํ์
์ผ๋ก ๋ณํํด์ผํ๋์ง ๋ชจ๋ฅด๊ธฐ๋๋ฌธ์ Jackson์ ๊ธฐ๋ณธ์ ์ผ๋ก LinkedHashMap
์ผ๋ก ์ญ์ง๋ ฌํํ๊ณ ์ญ์ง๋ ฌํ๋ ๊ฐ์ฒด์ ์ก์ธ์คํ๋ ค๊ณ ํ๋ฉด ClassCastException
์ด ๋ฐ์ํ๊ฒ๋๋ ๊ฒ์ด๋ค.
์ด๋ฅผ ํด๊ฒฐํ๋ ๋ฐฉ๋ฒ์ ๋ฐํ์์๋ ์๋ฐ JVM์ด ์ด๋ค ์๋ฐ ํ์
์ผ๋ก ๋ณํํ ์ง ์ ์ ์๋๋ก ํ์
์ ์๋ฉธํ์ง ์๊ณ ๋ณด์กดํด์ผํ๋ฉฐ, TypeReference
๊ฐ ๋ฐ๋ก ์ด๋ฌํ ์ญํ ์ ์ํํด์ค๋ค.
ObjectMapper
๋ JSON ๋ฌธ์์ด์์ ์๋ฐ Map ํ์
์ผ๋ก๋ ์ญ์ง๋ ฌํํ ์ ์๋ค.
Map์ผ๋ก ๋ณํํ๋ ๊ฒ์ ์ ํํ JSON ๊ตฌ์กฐ๋ฅผ ๋ฏธ๋ฆฌ ๋ชจ๋ฅด๋ ๊ฒฝ์ฐ ์ ์ฉํ ์ ์๋ค. JSON์ ๊ฐ ํ๋๋ ์๋ฐ Map์ key, value ์์ผ๋ก ๋ณํ๋๋ค.
@Test
void json_to_map() throws JsonProcessingException {
// given
ObjectMapper objectMapper = new ObjectMapper();
String json = "{\"id\":\"123\",\"name\":\"binghe\",\"age\":28}";
// when
Map<String, Object> jsonMap = objectMapper.readValue(json, new TypeReference<Map<String, Object>>(){});
// then
assertThat(jsonMap.get("id")).isEqualTo("123");
assertThat(jsonMap.get("name")).isEqualTo("binghe");
assertThat(jsonMap.get("age")).isEqualTo(28);
}
๋ค์์ ๋ค๋ฃฐ JsonNode
์ ๋น์ทํ ์ญํ ์ ์ํํ๋๋ก ์๋ฐ Map์ ์ฌ์ฉํ ์ ์๋ ๊ฒ์ด๋ค.
JSON๋ด์ ์กด์ฌํ๋ ํ๋๊ฐ ์๋ฐ ๊ฐ์ฒด์์ ํด๋น ํ๋๊ฐ ์๋ ๊ฒฝ์ฐ๊ฐ์๋ค.
Jackson์ ๋ํดํธ๋ก ์ด๋ฐ๊ฒฝ์ฐ ์์ธ๊ฐ ๋ฐ์ํ๋๋ฐ, ์๋ฐ ๊ฐ์ฒด๋ณด๋ค JSON์ ๋ ๋ง์ ํ๋๊ฐ ์กด์ฌํด๋ ์์ธ๊ฐ ๋ฐ์ํ์ง ์๋๋ก ํ๋ ค๋ฉด ์๋์ ๊ฐ์ด ์ค์ ํ๋ฉด๋๋ค.
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
์๋ฐ ๊ฐ์ฒด๋ด Primitive Type์ผ๋ก ๋ ์ํ๊ฐ์ด JSON๋ด ํ๋๊ฐ null์ธ ๊ฒฝ์ฐ Jackson์ Primitive Type์ ๋ํดํธ๋ก ๋ณํํ๋ค.
๋ง์ฝ JSON๋ด ํ๋๊ฐ null์ธ ์ํ๋ฅผ Primitive Type์ผ๋ก ๋ณํํ๊ณ ์ํ ๋ ์์ธ๋ฅผ ๋ฐ์์ํค๊ณ ์ถ์ผ๋ฉด ์๋์ ๊ฐ์ด ์ค์ ํด์ฃผ๋ฉด ๋๋ค.
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, true);
ObjectMapper
๊ฐ์ฒด๋ด ์ค๋ช
์ ๋ณด๋ฉด ์๋์ ๊ฐ์ด ๋ช
์๋์ด์๋ค.
Mapper (and ObjectReaders, ObjectWriters it constructs) will use instances of JsonParser and JsonGenerator for implementing actual reading/writing of JSON.