Computer >> 컴퓨터 >  >> 프로그래밍 >> Java

Java의 @JsonAnyGetter와 @JsonAnySetter 어노테이션 활용 방법

Jackson 라이브러리는 유연한 JSON 직렬화와 역직렬화를 위해 다양한 어노테이션을 제공하는데, 그중에서도 동적인 속성을 다룰 때 특히 유용한 것이 @JsonAnyGetter@JsonAnySetter입니다.

@JsonAnyGetter와 @JsonAnySetter란?

@JsonAnyGetter 어노테이션은 JSON으로 직렬화하고 싶은 속성들을 하나의 Map 객체에 컨테이너 형태로 담아 처리할 수 있도록 해줍니다. Map에 담긴 모든 항목들은 직렬화 시 개별 JSON 속성처럼 출력됩니다.

반면 @JsonAnySetter 어노테이션은 역직렬화 과정에서 JSON 객체 내의 인식되지 않는 필드(unrecognized fields), 즉 Java 객체에 미리 정의된 속성이나 setter 메서드로 매핑되지 않은 모든 필드에 대해 동일한 setter 메서드를 호출하도록 Jackson에 지시합니다.

문법(Syntax)

public @interface JsonAnyGetter
public @interface JsonAnySetter

두 어노테이션 모두 별도의 파라미터 없이 getter 또는 setter 메서드 위에 선언하여 사용합니다.

예제 코드

아래 예제는 Employee 클래스에 두 어노테이션을 적용하여 직렬화와 역직렬화가 어떻게 동작하는지 보여줍니다.

import java.io.*;
import java.util.*;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.annotation.*;

public class JsonAnyGetterAndJsonAnySetterTest {
    public static void main(String args[]) throws JsonGenerationException, JsonMappingException, IOException {
        Employee emp1 = new Employee();
        emp1.setFirstName("Adithya");
        emp1.setLastName("Sai");
        emp1.setEmpId(125);
        emp1.getAdditionalInformation().put("technology1", "Machine Learning");
        emp1.getAdditionalInformation().put("technology2", "Robotics");

        ObjectMapper mapper = new ObjectMapper();
        String jsonStr = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(emp1);
        System.out.println(jsonStr);

        System.out.println("Deserializing JSON to Object:");
        Employee emp2 = mapper.readValue(jsonStr, Employee.class);
        System.out.println("id : " + emp2.getEmpId());
        System.out.println("firstName : " + emp2.getFirstName());
        System.out.println("lastName : " + emp2.getLastName());
        System.out.println("Additional information : " + emp2.getAdditionalInformation());
    }
}

// Employee 클래스
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonPropertyOrder({"firstName", "lastName", "technologies", "empId"})
class Employee {
    @JsonProperty("EMPLOYEE_ID")
    private int empId;
    @JsonProperty("EMPLOYEE_FIRST_NAME")
    private String firstName;
    @JsonProperty("EMPLOYEE_LAST_NAME")
    private String lastName;
    private Map<String, String> additionalInformation = new HashMap<>();

    public int getEmpId() {
        return empId;
    }
    public void setEmpId(int empId) {
        this.empId = empId;
    }
    public String getFirstName() {
        return firstName;
    }
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
    public String getLastName() {
        return lastName;
    }
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    @JsonAnyGetter
    public Map<String, String> getAdditionalInformation() {
        return additionalInformation;
    }
    public void setAdditionalInformation(Map<String, String> additionalInformation) {
        this.additionalInformation = additionalInformation;
    }

    @JsonAnySetter
    public void setAdditionalProperty(final String name, final String value) {
        this.additionalInformation.put(name, value);
    }
}

실행 결과

직렬화 결과를 보면 Map에 담겨 있던 technology1technology2 항목이 일반 속성과 동일하게 JSON 최상위 레벨에 출력되는 것을 확인할 수 있습니다. 또한 역직렬화 시에도 이 값들이 @JsonAnySetter가 붙은 메서드를 통해 Map으로 수집됩니다.

{
  "EMPLOYEE_FIRST_NAME" : "Adithya",
  "EMPLOYEE_LAST_NAME" : "Sai",
  "EMPLOYEE_ID" : 125,
  "technology1" : "Machine Learning",
  "technology2" : "Robotics"
}
Deserializing JSON to Object:
id : 125
firstName : Adithya
lastName : Sai
Additional information : {technology1=Machine Learning, technology2=Robotics}

정리

이처럼 두 어노테이션은 스키마가 고정되지 않고 외부 데이터에 따라 속성이 달라지는 경우에 매우 유용합니다. 예를 들어 확장 가능한 메타데이터, 사용자 정의 설정값, API 응답의 가변 필드 등을 처리할 때 @JsonAnyGetter@JsonAnySetter를 활용하면 별도의 DTO 수정 없이 유연하게 대응할 수 있습니다.