[Jackson] 고급 직렬화와 역직렬화 설정을 알아보겠습니다. Jackson 라이브러리는 Java 객체와 JSON 간의 변환을 쉽고 유연하게 수행할 수 있도록 다양한 기능을 제공합니다. 특히 복잡한 객체 구조나 커스텀 설정이 필요한 경우에는 커스텀 직렬화/역직렬화 기능이 유용하게 쓰입니다. 이번 포스팅에서는 커스텀 직렬화 및 역직렬화 설정, 제네릭 타입 데이터 직렬화와 역직렬화, 중첩 객체 구조 처리 방법, 객체 그래프 관리와 무한 루프 문제 해결에 대해 설명하고 각각의 상황에 맞는 예제를 제시하겠습니다.
커스텀 직렬화/역직렬화 설정 (Custom Serializer/Deserializer)
커스텀 직렬화/역직렬화는 기본 변환 규칙이 아닌 사용자 정의 로직을 통해 Java 객체와 JSON 간의 변환을 제어할 수 있도록 합니다. Jackson에서는 JsonSerializer와 JsonDeserializer 클래스를 확장하여 원하는 방식으로 JSON 변환을 제어할 수 있습니다. 예를 들어, 비밀번호와 같은 민감한 정보는 JSON에서 마스킹하는 방식으로 표현할 수 있습니다.
커스텀 직렬화
아래는 사용자의 password 필드를 직렬화할 때 일부 마스킹하는 예제입니다.
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import java.io.IOException;
class User {
private String name;
@JsonSerialize(using = PasswordSerializer.class)
private String password;
public User() {}
public User(String name, String password) {
this.name = name;
this.password = password;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
class PasswordSerializer extends JsonSerializer<String> {
@Override
public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeString("****" + value.substring(value.length() - 2));
}
}
public class Main {
public static void main(String[] args) throws Exception {
User user = new User("홍길동", "password1234");
ObjectMapper mapper = new ObjectMapper();
String jsonResult = mapper.writeValueAsString(user);
System.out.println("JSON 출력: " + jsonResult);
}
}
이 예제에서는 PasswordSerializer를 통해 비밀번호 필드를 마스킹하여 출력하고 있습니다. JsonSerializer를 확장하고 @JsonSerialize 어노테이션을 사용하여 커스텀 직렬화 방식을 적용했습니다.
커스텀 역직렬화
역직렬화는 JSON에서 특정 포맷을 Java 객체로 변환할 때 유용하게 사용됩니다. 예를 들어, 날짜를 특정 포맷으로 JSON에 저장하고 이를 Java의 LocalDate 객체로 변환할 수 있습니다.
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import java.io.IOException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
class Event {
private String name;
@JsonDeserialize(using = DateDeserializer.class)
private LocalDate eventDate;
public Event() {}
public Event(String name, LocalDate eventDate) {
this.name = name;
this.eventDate = eventDate;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public LocalDate getEventDate() {
return eventDate;
}
public void setEventDate(LocalDate eventDate) {
this.eventDate = eventDate;
}
}
class DateDeserializer extends JsonDeserializer<LocalDate> {
@Override
public LocalDate deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
return LocalDate.parse(p.getText(), formatter);
}
}
public class Main {
public static void main(String[] args) throws Exception {
String json = "{\"name\":\"생일파티\", \"eventDate\":\"2023-10-25\"}";
ObjectMapper mapper = new ObjectMapper();
Event event = mapper.readValue(json, Event.class);
System.out.println("이벤트 이름: " + event.getName());
System.out.println("이벤트 날짜: " + event.getEventDate());
}
}
DateDeserializer 클래스를 통해 JSON의 문자열을 LocalDate 객체로 변환하고 있습니다. Jackson에서 JsonDeserializer를 활용하여 JSON 문자열을 원하는 객체로 역직렬화할 수 있습니다.
제네릭 타입 데이터 직렬화와 역직렬화
Java에서는 제네릭 타입 데이터를 다룰 때 Jackson이 JSON 변환 시 제네릭 정보를 유지하기 어렵습니다. 이를 해결하기 위해 TypeReference를 사용하여 제네릭 타입을 명확히 지정할 수 있습니다.
제네릭 타입 리스트의 직렬화 및 역직렬화
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.List;
class Employee {
private String name;
private String position;
public Employee() {}
public Employee(String name, String position) {
this.name = name;
this.position = position;
}
public String getName() {
return name;
}
public String getPosition() {
return position;
}
}
public class Main {
public static void main(String[] args) throws Exception {
String json = "[{\"name\":\"김철수\", \"position\":\"개발자\"}, {\"name\":\"이영희\", \"position\":\"디자이너\"}]";
ObjectMapper mapper = new ObjectMapper();
List<Employee> employees = mapper.readValue(json, new TypeReference<List<Employee>>() {});
employees.forEach(employee -> System.out.println("이름: " + employee.getName() + ", 직책: " + employee.getPosition()));
}
}
이 예제에서는 JSON 배열을 List<Employee>로 역직렬화하고 있습니다. TypeReference를 사용하여 제네릭 타입 정보를 제공하여 정확한 타입으로 변환이 가능합니다.
복잡한 객체 구조를 다루는 방법 (Nested Object 처리)
Jackson은 중첩 객체 구조를 다루는 기능도 제공합니다. 특히, 복잡한 JSON 구조를 Java의 중첩 객체로 변환할 때 @JsonProperty와 객체 생성자를 사용하여 데이터를 쉽게 매핑할 수 있습니다.
중첩 객체 구조 처리
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
class Address {
private String city;
private String street;
public Address() {}
public Address(String city, String street) {
this.city = city;
this.street = street;
}
public String getCity() {
return city;
}
public String getStreet() {
return street;
}
}
class Person {
private String name;
@JsonProperty("address_info")
private Address address;
public Person() {}
public Person(String name, Address address) {
this.name = name;
this.address = address;
}
public String getName() {
return name;
}
public Address getAddress() {
return address;
}
}
public class Main {
public static void main(String[] args) throws Exception {
String json = "{\"name\":\"이순신\", \"address_info\":{\"city\":\"서울\", \"street\":\"중구\"}}";
ObjectMapper mapper = new ObjectMapper();
Person person = mapper.readValue(json, Person.class);
System.out.println("이름: " + person.getName());
System.out.println("도시: " + person.getAddress().getCity());
System.out.println("거리: " + person.getAddress().getStreet());
}
}
객체 그래프 관리와 무한 루프 문제 해결법
Jackson은 객체 그래프 관리 시 무한 루프 문제가 발생할 수 있습니다. 예를 들어, 양방향 관계를 가진 객체를 JSON으로 변환할 때 참조가 반복되어 StackOverflowError가 발생할 수 있습니다. @JsonManagedReference와 @JsonBackReference를 사용하여 순환 참조 문제를 해결할 수 있습니다.
@JsonManagedReference와 @JsonBackReference를 통한 순환 참조 해결
import com.fasterxml.jackson.annotation.JsonBackReference;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.ArrayList;
import java.util.List;
class Department {
private String name;
@JsonManagedReference
private List<Employee> employees;
public Department(String name) {
this.name = name;
this.employees = new ArrayList<>();
}
public void addEmployee(Employee employee) {
employees.add(employee);
employee.setDepartment(this);
}
}
class Employee {
private String name;
@JsonBackReference
private Department department;
public Employee(String name) {
this.name = name;
}
public void setDepartment(Department department) {
this.department = department;
}
}
public class Main {
public static void main(String[] args) throws Exception {
Department department = new Department("개발팀");
Employee employee1 = new Employee("홍길동");
department.addEmployee(employee1);
ObjectMapper mapper = new ObjectMapper();
String jsonResult = mapper.writeValueAsString(department);
System.out.println("JSON 출력: " + jsonResult);
}
}
@JsonManagedReference와 @JsonBackReference를 통해 JSON 변환 시 참조 방향을 지정하여 무한 루프를 방지했습니다.
참고 사이트
'JAVA' 카테고리의 다른 글
[JAVA] Jackson과 Java 8 이상 기능 연동 (2) | 2024.11.12 |
---|---|
[JAVA] Jackson의 Data Binding API와 Tree Model API 차이점 (0) | 2024.11.12 |
[JAVA] Jackson Tree Model API 활용하기 (1) | 2024.11.12 |
[JAVA] Jackson 컬렉션 및 배열 데이터 처리 (1) | 2024.11.12 |
[JAVA] Jackson 어노테이션 활용법 (0) | 2024.11.10 |
[JAVA] Jackson ObjectMapper란? (1) | 2024.11.09 |
[JAVA] Jackson 환경 설정 (1) | 2024.11.08 |
[JAVA] Jackson 이란? (0) | 2024.11.07 |