2019. 8. 5. 17:47ㆍ[개발] 기록
* 무한 재귀 참조를 방지하는 다른 해결책으로는 다음과 같은 것들이있다.
- JsonIgnore
- JsonIdentityInfo
- 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 |