kok202
Already had POJO for id 에러

2019. 8. 5. 17:47[개발] 기록

* 무한 재귀 참조를 방지하는 다른 해결책으로는 다음과 같은 것들이있다.

  1. JsonIgnore 
  2. JsonIdentityInfo 
  3. JsonManagedReference & JsonBackReference 

JsonIdentityInfo : deserialize 할 때 객체간에 양방향 참조를 하는 경우 무한 재귀 참조가 되는 문제를 해결하기 위해 나온 어노테이션. 객체 내부에 Id 필드와 같이 unique 한 값이 있을 때 사용할 수 있다. Jackson 에서 요청받은 json 을 deserialize 를 수행할 때 같은 타입에 대하여 Id 필드가 같은 json 요청이 발견될 경우 기존에 deserialize 해놓은 객체를 이용한다.

중요) JsonIdentityInfo 의 serialize 형태는 흔히 생각하는 json serialize 형태와 다르다.

 

 

 

 

 

가정

@Data
public class ParentEntity {
    private long id;
    private ArrayList<ChildEntity> childEntities;
}
@Data
@JsonIdentityInfo(
    generator = ObjectIdGenerators.PropertyGenerator.class,
    property = "id",
    scope = ChildEntity.class)
public class ChildEntity {
    private long id;
    private String name;
}

 

 

 

 

 

1. 정상적인 상황

public class MainJsonIdentityInfo {
    public static void main(String[] args) throws Exception{
        ArrayList<ChildEntity> childEntities = new ArrayList<>();
        ChildEntity childEntity1 = new ChildEntity();
        childEntity1.setId(456);
        childEntity1.setName("hello");
        childEntities.add(childEntity1);
        childEntities.add(childEntity1);

        ParentEntity parentEntity = new ParentEntity();
        parentEntity.setId(123);
        parentEntity.setChildEntities(childEntities);

        ObjectMapper objectMapper = new ObjectMapper();
        String json = objectMapper.writeValueAsString(parentEntity);
        System.out.println(json);

        ParentEntity deserializedParentEntity = objectMapper.readValue(json, ParentEntity.class);
        System.out.println(deserializedParentEntity);
    }
}
{"id":123,"childEntities":[{"id":456,"name":"hello"},456]}
ParentEntity(id=123, childEntities=[ChildEntity(id=456, name=hello), ChildEntity(id=456, name=hello)])

serialize 가 무사히 됬다. 특이한 점은 아래와 같이 serialize 된 게 아니라

{"id":123,"childEntities":[{"id":456,"name":"hello"},{"id":456,"name":"hello"}]}

아래와 같이 serialize 됬다는 점이다.

{"id":123,"childEntities":[{"id":456,"name":"hello"},456]}

JsonIdentityInfo 의 동작 원리를 추측해보자면 : JsonIdentityInfo 가 지정된 타입을 deserialize 할 때, 이 타입의 자리에 Object 가 아니라 단일 하게 들어오는 값이 있을 경우 처리를 다르게 하는 듯하다. Deserialize 하면서 생성한 객체 중 들어온 단일 필드 값과 IdentityProperty 가 일치하는 객체를 찾고 이를 referece type 으로 건내주는 듯 싶다.

 

 

 

 

 

 

2. 문제 상황

public class MainJsonIdentityInfo {
    public static void main(String[] args) throws Exception{
        ArrayList<ChildEntity> childEntities = new ArrayList<>();
        ChildEntity childEntity1 = new ChildEntity();
        childEntity1.setId(456);
        childEntity1.setName("hello");
        childEntities.add(childEntity1);
        ChildEntity childEntity2 = new ChildEntity();
        childEntity2.setId(456);
        childEntity2.setName("hello");
        childEntities.add(childEntity2);

        ParentEntity parentEntity = new ParentEntity();
        parentEntity.setId(123);
        parentEntity.setChildEntities(childEntities);

        ObjectMapper objectMapper = new ObjectMapper();
        String json = objectMapper.writeValueAsString(parentEntity);
        System.out.println(json);

        ParentEntity deserializedParentEntity = objectMapper.readValue(json, ParentEntity.class);
        System.out.println(deserializedParentEntity);
    }
}
{"id":123,"childEntities":[{"id":456,"name":"hello"},{"id":456,"name":"hello"}]}
Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: Already had POJO for id (java.lang.Long)
...

Serialize 는 무사히 성공한 것처럼 보인다. 하지만 같은 객체에서 deserialize 하는 것이 아니라서 위의 serialize 결과와 다르다. Jackson 은 json string 을 deserialize 하려한다. 뒤에 있는 {"id":456,"name":"hello"} 를  deserialize 하려보니 ChildEntity 는 JsonIdentityInfo 가 지정되어있다. deserialize 한 객체중 ChildEntity 타입인 객체를 찾는다. 이때 Id 가 456 인 객체가 이미 deserialize 됬음을 확인한다. 이에 Error 를 내뱉는다.

 

 

 

 

 

3. 당연히 문제 상황

public class MainJsonIdentityInfo {
    public static void main(String[] args) throws Exception{
        ArrayList<ChildEntity> childEntities = new ArrayList<>();
        ChildEntity childEntity1 = new ChildEntity();
        childEntity1.setId(456);
        childEntity1.setName("hello");
        childEntities.add(childEntity1);
        ChildEntity childEntity2 = new ChildEntity();
        childEntity2.setId(456);
        childEntity2.setName("world");
        childEntities.add(childEntity2);

        ParentEntity parentEntity = new ParentEntity();
        parentEntity.setId(123);
        parentEntity.setChildEntities(childEntities);

        ObjectMapper objectMapper = new ObjectMapper();
        String json = objectMapper.writeValueAsString(parentEntity);
        System.out.println(json);

        ParentEntity deserializedParentEntity = objectMapper.readValue(json, ParentEntity.class);
        System.out.println(deserializedParentEntity);
    }
}
{"id":123,"childEntities":[{"id":456,"name":"hello"},{"id":456,"name":"world"}]}
Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: Already had POJO for id (java.lang.Long) 
...

당연히 field 값이 다르면 더더욱 안된다.

 

 

 

 

 

4. 정상 동작

public class MainJsonIdentityInfo {
    public static void main(String[] args) throws Exception{
        ObjectMapper objectMapper = new ObjectMapper();
        String json = "{\"id\":123,\"childEntities\":[{\"id\":456,\"name\":\"hello\"},456,456,456,456,456]}";

        ParentEntity deserializedParentEntity = objectMapper.readValue(json, ParentEntity.class);
        System.out.println(deserializedParentEntity);
    }
}
ParentEntity(id=123, childEntities=[ChildEntity(id=456, name=hello), ChildEntity(id=456, name=hello), ChildEntity(id=456, name=hello), ChildEntity(id=456, name=hello), ChildEntity(id=456, name=hello), ChildEntity(id=456, name=hello)])

반면 위 코드는 정상 동작한다.

 

 

 

 

 

'[개발] 기록' 카테고리의 다른 글

Parallel stream 과 Map  (0) 2020.01.07
JsonTypeInfo, JsonSubTypes : 필드 값에 따라 파싱 타입 결정하기  (0) 2019.10.23
mvn vs mvnw  (0) 2019.06.26
자바 8 Stream  (0) 2019.06.06
자바의 병렬 처리 발전사  (0) 2019.05.03