Spring Boot와 같은 대다수 Web Framework에서 Controller에서 Parameter 또는 Return value로 클래스 타입으로 지정하면 매우 간편하게 serialization이 되어 우리가 원하는 결과로 나타내줍니다. 그걸 가능하게 하는 건 _jackson_이라는 JSON library를 이용해서 인데, 일반적으로 json으로 serialize하는 대상은 value object일 경우가 대다수일텐데 (아닌 경우가 있을까요?..) 자바와 같이 멀티스래딩 환경에서는 해당 객체를 immutable서로 하는 것이 안전합니다.

The advantage of immutability comes with concurrency. It is difficult to maintain correctness in mutable objects, as multiple threads could be trying to change the state of the same object, leading to some threads seeing a different state of the same object, depending on the timing of the reads and writes to the said object.

By having an immutable object, one can ensure that all threads that are looking at the object will be seeing the same state, as the state of an immutable object will not change.

(출처: http://stackoverflow.com/questions/3162665/immutable-class)

public class Position {
    private int x;
    private int y;

    public Position(int x, int y) {
        this.x = x;
        this.y = y;
    }
}

위와 같은 클래스가 있을 때 이를 다음과 같이 테스트를 해보면 에러가 발생합니다.

public class JsonCreateTest {

    private ObjectMapper objectMapper = new ObjectMapper();;

    @Before
    public void setUp() throws Exception {
        objectMapper = new ObjectMapper();
    }

    private <T> String toJson(T object) throws JsonProcessingException {
        return objectMapper.writeValueAsString(object);
    }

    @Test
    public void position() throws Exception {
        Position subject = new Position(1, 2);

        String json = toJson(subject);
        assertThat(json).isEqualTo("{\"x\":1,\"y\":2}");
    }
}
com.fasterxml.jackson.databind.JsonMappingException: No serializer found for class omok.engine.Position and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) )
	at com.fasterxml.jackson.databind.ser.impl.UnknownSerializer.failForEmpty(UnknownSerializer.java:69)
	at com.fasterxml.jackson.databind.ser.impl.UnknownSerializer.serialize(UnknownSerializer.java:32)
	at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:130)
	at com.fasterxml.jackson.databind.ObjectMapper._configAndWriteValue(ObjectMapper.java:3559)
	at com.fasterxml.jackson.databind.ObjectMapper.writeValueAsString(ObjectMapper.java:2927)
	at omok.engine.JsonCreateTest.toJson(JsonCreateTest.java:20)

jackson 은 기본적으로 parameter가 없는 기본 생성자를 필요로 합니다. 해당 인스턴스를 생성하기 위해서 말이죠. 그 후에 하위 필드의 값이나 또 다른 클래스 인스턴스들을 serialize하면서 읽어들이고 있습니다. 구글링을 조금 하면 위 Position 클래스는 다음과 같이 수정할 수 없습니다.

public class Position {
    private final int x;
    private final int y;

    @JsonCreator
    public Position(@JsonProperty("x") int x, @JsonProperty("y") int y) {
        this.x = x;
        this.y = y;
    }
}

하지만 같은 오류가 발생합니다. 왜일까요?

답은 JSON serialize 대상인 필드들을 접근 할 수 없어서 입니다. Position 클래스를 Inner class로 선언했거나 값을 꺼내서 핸들링하지 않아서 그대로 private으로 두었지만 Jackson library가 serialize하는 과정 중에 해당 필드를 접근하는 듯 합니다. 따라서 다음과 같이 두가지 방법으로 노출시켜야 합니다.

  • 대상 필드를 public으로 노출
  • 대상 필드에 대한 getter 생성
public class Position {
    public final int x;
    public final int y;

    @JsonCreator
    public Position(@JsonProperty("x") int x, @JsonProperty("y") int y) {
        this.x = x;
        this.y = y;
    }
}

저는 Immutable class에는 getter보다 직접 접근하는 게 좋더라구요. 🙂