[Jackson] Jackson 파싱 전략(불변 객체 활용)

2020. 7. 12. 16:23Java

웹 프로그래밍을 하다보면 Json을 객체로, 객체를 Json으로 변환해야 할 일이 많다. Spring boot는 이러한 오브젝트 매핑의 기본 전략으로 Jackson을 사용한다. 별다른 설정 없이 Spring boot를 사용한다면 Jackson을 사용하고 있는 것이다.

ObjectMapper 활용

Jackson은 오브젝트 매핑을 위해 ObjectMapper라는 객체를 제공한다. new ObjectMapper()를 통해 생성할 수도 있고, 스프링 컨테이너를 통해 주입받을 수도 있다.
객체를 JsonString으로 변환하고 싶으면 Objectmapper.writeValueAsString(object) 메서드를 사용하면 되고, 반대로 JsonString에서 객체로 변환하고 싶으면 ObjectMapper.readValue(jsonString, JsonObject.class) 메서드를 사용하면 된다.

예제 코드를 통해 알아보자.

// User.java
@AllArgConstructor
@Getter
public class User {
    private String userName;

    private int age;
}
{
    user_name: "장효혁",
    age: 26
}

자바 클래스인 User와 위 Json을 매핑하려 한다. 먼저 테스트 코드를 짜보자.

@Test
void parsingSuccess() throws JsonProcessingException {
    ObjectMapper objectMapper = new ObjectMapper();

    User user = new User("장효혁", 26);

    String jsonString = objectMapper.writeValueAsString(user);

    User parsedUser = objectMapper.readValue(jsonString, User.class);

    assertAll(
        () -> assertThat(jsonString).isEqualTo("{\"user_name\":\"장효혁\",\"age\":26}"),
        () -> assertThat(parsedUser.getUserName()).isEqualTo("장효혁"),
        () -> assertThat(parsedUser.getAge()).isEqualTo(26)
    );
}

테스트 코드를 돌려보면 아래와 같은 예외가 나온다.
Cannot construct instance of 'com.example.webflux.jackson.User' (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
ObjectMapper는 클래스에 기본 생성자가 있으면 파싱을 할 수 있다. User 클래스에 기본 생성자를 추가해준다.

// User.java
@NoArgConstructor    // 기본 생성자 추가
@AllArgConstructor
@Getter
public class User {
    private String userName;

    private int age;
}

다시 테스트를 돌려보면 여전히 깨진다. 변환된 JsonString을 잘 살펴보면 {userName: "장효혁", age: 26}처럼 나온다. 우리가 원하는 형태는 user_name이다. Jackson은 이를 위한 변환 방법을 제시한다.

// User.java
@NoArgConstructor
@AllArgConstructor
@Getter
public class User {
    @JsonProperty("user_name")    // JsonString으로 사용할 key 추가
    private String userName;

    private int age;
}

이제 테스트를 돌리면 통과하는 화면을 볼 수 있다.

불변 객체

위 예제에서는 클래스에 기본 생성자를 추가했다. 하지만 모든 필드에 final이 붙는 불변 객체에서는 기본 생성자를 추가할 수 없다. 아래는 기본 생성자 없이 Jackson이 객체를 파싱하는 방법을 소개한다.

// User.java
@Getter
public class User {
    @JsonProperty("user_name")
    private final String userName;

    private final int age;

    @JsonCreator // 생성자가 하나면 생략 가능
    public User(@JsonProperty("user_name") String userName,
                @JsonProperty("age") int age) {

                this.userName = userName;
                this.age = age;
    }
}

파싱에 필요한 필드를 갖는 생성자에 @JsonCreator를 붙여 Jackson에게 이를 사용하라고 알려준다. 만일 클래스에 생성자가 하나뿐이라면 이를 생략해도 된다.
다음은, 생성자 내에 파라미터 모두에 @JsonProperty를 붙여 Jackson에게 접근할 필드명을 알려준다. 테스트를 돌리면 통과 화면을 확인할 수 있다.

lombok을 사용하고 싶다면 아래와 같이 할 수 있다.

// User.java
@AllArgsConstructor(onConstructor_ = {@ConstructorProperties({"user_name", "age"})})
@Getter
public class User {
    @JsonProperty("user_name")
    private final String userName;

    private final int age;
}

@ConstructorProperties는 생성자 내 파라미터에 모두 @JsonProperty를 붙이기 않기 위해 Jackson에서 지원하는 방식이다.