최대 절전 양방향 매핑으로 인한 json serializer의 순환 참조를 해결하는 방법은 무엇입니까?


82

POJO를 JSON으로 직렬화하는 serializer를 작성 중이지만 순환 참조 문제에 갇혀 있습니다. 최대 절전 모드 양방향 일대 다 관계에서 부모는 자식 및 자식 참조를 부모로 다시 참조하고 여기서 내 직렬 변환기가 죽습니다. (아래 예제 코드 참조)
이주기를 끊는 방법은 무엇입니까? 개체의 소유자 트리를 가져와 개체 자체가 소유자 계층의 어딘가에 있는지 확인할 수 있습니까? 참조가 순환되는지 확인하는 다른 방법이 있습니까? 또는이 문제를 해결하기위한 다른 아이디어가 있습니까?


문제 해결에 도움이되도록 일부 코드를 붙여 넣으려고 하셨나요?
Russell

2
eugene의 주석 기반 솔루션은 괜찮지 만이 경우 추가 주석 및 ExclusionStrategy 구현이 필요하지 않습니다. Java ' transient '키워드를 사용하십시오. 그것은 표준 자바 객체 직렬화를 위해 작동하지만 Gson그것을 존중합니다 .
MeTTeO

답변:


11

양방향 관계를 JSON으로 표현할 수도 있습니까? 일부 데이터 형식은 일부 데이터 모델링 유형에 적합하지 않습니다.

순회 개체 그래프를 처리 할 때주기를 처리하는 한 가지 방법은 지금까지 본 개체를 추적하여 (ID 비교 사용) 무한주기를 통과하지 못하도록하는 것입니다.


나는 똑같이했고 작동하지만 모든 매핑 시나리오에서 작동하는지 확실하지 않습니다. 지금은 적어도 나는 잘 설정하고 더 우아하고 생각하는 데에 생각을 유지합니다
WSK

물론 그들은 할 수 있습니다-이것에 대한 기본 데이터 유형이나 구조가 없습니다. 그러나 XML, JSON 또는 대부분의 다른 데이터 형식으로 표현할 수 있습니다.
StaxMan

4
궁금합니다. JSON에서 순환 참조를 어떻게 표현할까요?
matt b

1
여러 가지 방법 : 일반적으로 JSON뿐만 아니라 JSON과 일부 메타 데이터의 조합이며, 가장 일반적으로 JSON과 바인딩하는 데 사용하는 클래스 정의입니다. JSON의 경우 어떤 종류의 객체 ID를 사용할지 아니면 연결을 다시 만들지에 대한 질문 일뿐입니다 (예를 들어 Jackson lib에는 부모 / 자식 연결을 나타내는 특정 방법이 있습니다).
StaxMan

46

이 기능을 사용하여 이러한 종류의 문제를 처리하기 위해 Google JSON 에 의존 합니다.

직렬화 및 역 직렬화에서 필드 제외

다음과 같이 A와 B 클래스 사이의 양방향 관계를 가정하십시오.

public class A implements Serializable {

    private B b;

}

그리고 B

public class B implements Serializable {

    private A a;

}

이제 GsonBuilder를 사용하여 다음과 같이 사용자 지정 Gson 개체를 가져옵니다 (Notice setExclusionStrategies 메서드).

Gson gson = new GsonBuilder()
    .setExclusionStrategies(new ExclusionStrategy() {

        public boolean shouldSkipClass(Class<?> clazz) {
            return (clazz == B.class);
        }

        /**
          * Custom field exclusion goes here
          */
        public boolean shouldSkipField(FieldAttributes f) {
            return false;
        }

     })
    /**
      * Use serializeNulls method if you want To serialize null values 
      * By default, Gson does not serialize null values
      */
    .serializeNulls()
    .create();

이제 순환 참조

A a = new A();
B b = new B();

a.setB(b);
b.setA(a);

String json = gson.toJson(a);
System.out.println(json);

GsonBuilder 클래스 살펴보기


친절한 제안에 대해 Arthur에게 감사하지만 실제 질문은 소위 일반적인 "shouldSkipClass"메소드를 빌드하는 가장 좋은 방법이 무엇인지입니다. 지금은 Matt의 아이디어로 작업하고 문제를 해결했지만 여전히 회의적입니다. 앞으로이 솔루션은 인증 시나리오에서 중단 될 수 있습니다.
WSK

8
이렇게하면 순환 참조를 제거하여 "해결"합니다. 생성 된 JSON에서 원래 데이터 구조를 다시 빌드 할 수있는 방법은 없습니다.
Sotirios Delimanolis 2015

34

Jackson 1.6 (2010 년 9 월 출시)에는 이러한 부모 / 자식 연결을 처리하기위한 특정 주석 기반 지원이 있습니다. http://wiki.fasterxml.com/JacksonFeatureBiDirReferences를 참조 하십시오 . ( 웨이 백 스냅 샷 )

물론 이미 대부분의 JSON 처리 패키지 (jackson, gson 및 flex-json이 지원)를 이미 사용하고있는 상위 링크의 직렬화를 제외 할 수 있지만 실제 트릭은 다시 직렬화 (상위 링크 다시 생성)하는 방법이 아닙니다. 직렬화 측면을 처리하십시오. 지금은 제외가 효과가있을 수 있습니다.

편집 (2012 년 4 월) : Jackson 2.0은 이제 진정한 ID 참조 ( Wayback Snapshot )를 지원 하므로이 방법으로도 해결할 수 있습니다.


방향이 항상 같지 않을 때이 작업을 수행하는 방법 .. 두 필드에 두 주석을 모두 입력했지만 작동하지 않았습니다. Class A {@JsonBackReference ( "abc") @JsonManagedReference ( "xyz") 개인 B b; } 클래스 B {@JsonManagedReference ( "abc") @JsonBackReference ( "xyz") private A a; }
azi

위와 같이 Object Id (@JsonIdentityInfo)는 일반 참조가 작동하도록하는 방법입니다. 관리 / 뒤로 참조에는 특정 지침이 필요하므로 귀하의 경우에는 작동하지 않습니다.
StaxMan 2014

12

이 문제를 해결하기 위해 다음과 같은 접근 방식을 취했습니다 (애플리케이션 전체에서 프로세스를 표준화하여 코드를 명확하고 재사용 가능하게 함).

  1. 제외하려는 필드에 사용할 주석 클래스를 만듭니다.
  2. Google의 ExclusionStrategy 인터페이스를 구현하는 클래스 정의
  3. GsonBuilder를 사용하여 GSON 객체를 생성하는 간단한 방법을 만듭니다 (Arthur의 설명과 유사).
  4. 필요에 따라 제외 할 필드에 주석을 답니다.
  5. com.google.gson.Gson 객체에 직렬화 규칙을 적용합니다.
  6. 개체 직렬화

코드는 다음과 같습니다.

1)

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD})
public @interface GsonExclude {

}

2)

import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;

public class GsonExclusionStrategy implements ExclusionStrategy{

    private final Class<?> typeToExclude;

    public GsonExclusionStrategy(Class<?> clazz){
        this.typeToExclude = clazz;
    }

    @Override
    public boolean shouldSkipClass(Class<?> clazz) {
        return ( this.typeToExclude != null && this.typeToExclude == clazz )
                    || clazz.getAnnotation(GsonExclude.class) != null;
    }

    @Override
    public boolean shouldSkipField(FieldAttributes f) {
        return f.getAnnotation(GsonExclude.class) != null;
    }

}

삼)

static Gson createGsonFromBuilder( ExclusionStrategy exs ){
    GsonBuilder gsonbuilder = new GsonBuilder();
    gsonbuilder.setExclusionStrategies(exs);
    return gsonbuilder.serializeNulls().create();
}

4)

public class MyObjectToBeSerialized implements Serializable{

    private static final long serialVersionID = 123L;

    Integer serializeThis;
    String serializeThisToo;
    Date optionalSerialize;

    @GsonExclude
    @ManyToOne(fetch=FetchType.LAZY, optional=false)
    @JoinColumn(name="refobj_id", insertable=false, updatable=false, nullable=false)
    private MyObjectThatGetsCircular dontSerializeMe;

    ...GETTERS AND SETTERS...
}

5)

첫 번째 경우 생성자에 null이 제공되며 제외 할 다른 클래스를 지정할 수 있습니다. 두 옵션 모두 아래에 추가됩니다.

Gson gsonObj = createGsonFromBuilder( new GsonExclusionStrategy(null) );
Gson _gsonObj = createGsonFromBuilder( new GsonExclusionStrategy(Date.class) );

6)

MyObjectToBeSerialized _myobject = someMethodThatGetsMyObject();
String jsonRepresentation = gsonObj.toJson(_myobject);

또는 Date 개체를 제외하려면

String jsonRepresentation = _gsonObj.toJson(_myobject);

1
이렇게하면 자식 개체가 처리되지 않습니다. 자식 json을 포함시킨 다음 순환을 중지 할 수
있습니까

3

Jackon을 사용하여 직렬화하는 경우 @JsonBackReference 를 양방향 매핑에 적용 하면 순환 참조 문제가 해결됩니다.

참고 : @JsonBackReference는 무한 재귀 (StackOverflowError)를 해결하는 데 사용됩니다.


@JsonIgnore의 나 만들어 JpaRepository속성을 매핑하는 데 실패하지만 @JsonBackReference여전히 순환 참조를 해결하고 문제가있는 속성에 대한 적절한 매핑을 허용
Bramastic

2

대신 아서의,하지만 비슷한 솔루션을 사용 setExclusionStrategies내가 사용

Gson gson = new GsonBuilder()
                .excludeFieldsWithoutExposeAnnotation()
                .create();

@Exposejson에서 필요한 필드에 gson 주석을 사용 하면 다른 필드는 제외됩니다.


고마워 친구. 이것은 나를 위해 일했습니다. 오랜 연구 끝에 마침내이 오류에서 벗어날 방법을 찾았습니다.
kepy97

2

스프링 부트를 사용하는 경우 Jackson은 순환 / 양방향 데이터에서 응답을 생성하는 동안 오류가 발생하므로

안녕하세요.

순환 성을 무시하다

At Parent:
@OneToMany(mappedBy="dbApp")
@JsonIgnoreProperties("dbApp")
private Set<DBQuery> queries;

At child:
@ManyToOne
@JoinColumn(name = "db_app_id")
@JsonIgnoreProperties("queries")
private DBApp dbApp;

1

Javascript를 사용하는 경우, 기본 직렬화 동작을 수정하는 함수를 전달할 수있는 method replacer매개 변수 를 사용하는 것에 대한 매우 쉬운 솔루션 JSON.stringify()이 있습니다.

사용 방법은 다음과 같습니다. 순환 그래프에 4 개의 노드가있는 아래 예제를 고려하십시오.

// node constructor
function Node(key, value) {
    this.name = key;
    this.value = value;
    this.next = null;
}

//create some nodes
var n1 = new Node("A", 1);
var n2 = new Node("B", 2);
var n3 = new Node("C", 3);
var n4 = new Node("D", 4);

// setup some cyclic references
n1.next = n2;
n2.next = n3;
n3.next = n4;
n4.next = n1;

function normalStringify(jsonObject) {
    // this will generate an error when trying to serialize
    // an object with cyclic references
    console.log(JSON.stringify(jsonObject));
}

function cyclicStringify(jsonObject) {
    // this will successfully serialize objects with cyclic
    // references by supplying @name for an object already
    // serialized instead of passing the actual object again,
    // thus breaking the vicious circle :)
    var alreadyVisited = [];
    var serializedData = JSON.stringify(jsonObject, function(key, value) {
        if (typeof value == "object") {
            if (alreadyVisited.indexOf(value.name) >= 0) {
                // do something other that putting the reference, like 
                // putting some name that you can use to build the 
                // reference again later, for eg.
                return "@" + value.name;
            }
            alreadyVisited.push(value.name);
        }
        return value;
    });
    console.log(serializedData);
}

나중에 직렬화 된 데이터를 구문 분석 하고이 예제에서 비슷한 next이름의 참조를 사용하는 경우 실제 객체를 가리 키도록 속성을 수정하여 순환 참조가있는 실제 객체를 쉽게 다시 만들 수 있습니다 @.


1

이것이 제 경우에 마침내 해결 한 방법입니다. 이것은 적어도 Gson & Jackson과 함께 작동합니다.

private static final Gson gson = buildGson();

private static Gson buildGson() {
    return new GsonBuilder().addSerializationExclusionStrategy( getExclusionStrategy() ).create();  
}

private static ExclusionStrategy getExclusionStrategy() {
    ExclusionStrategy exlStrategy = new ExclusionStrategy() {
        @Override
        public boolean shouldSkipField(FieldAttributes fas) {
            return ( null != fas.getAnnotation(ManyToOne.class) );
        }
        @Override
        public boolean shouldSkipClass(Class<?> classO) {
            return ( null != classO.getAnnotation(ManyToOne.class) );
        }
    };
    return exlStrategy;
} 

0

이 오류는 두 개의 개체가있을 때 나타날 수 있습니다.

class object1{
    private object2 o2;
}

class object2{
    private object1 o1;
}

직렬화에 GSon을 사용하면 다음 오류가 발생합니다.

java.lang.IllegalStateException: circular reference error

Offending field: o1

이 문제를 해결하려면 transient 키워드를 추가하십시오.

class object1{
    private object2 o2;
}

class object2{
    transient private object1 o1;
}

여기에서 볼 수 있듯이 Java에는 왜 임시 필드가 있습니까?

Java의 transient 키워드는 필드가 직렬화되지 않아야 함을 표시하는 데 사용됩니다.


0

Jackson은 JsonIdentityInfo순환 참조를 방지하기 위해 주석을 제공합니다 . 여기 에서 튜토리얼을 확인할 수 있습니다 .


-3

대답 번호 8이 더 낫다고 생각합니다. 어떤 필드가 오류를 던지고 있는지 알고 있다면 필드를 null로 설정하고 해결했습니다.

List<RequestMessage> requestMessages = lazyLoadPaginated(first, pageSize, sortField, sortOrder, filters, joinWith);
    for (RequestMessage requestMessage : requestMessages) {
        Hibernate.initialize(requestMessage.getService());
        Hibernate.initialize(requestMessage.getService().getGroupService());
        Hibernate.initialize(requestMessage.getRequestMessageProfessionals());
        for (RequestMessageProfessional rmp : requestMessage.getRequestMessageProfessionals()) {
            Hibernate.initialize(rmp.getProfessional());
            rmp.setRequestMessage(null); // **
        }
    }

코드를 읽기 쉽게 만들기 위해 큰 주석이 주석 // **에서 아래 로 이동되었습니다 .

java.lang.StackOverflowError [요청 처리 실패; 중첩 된 예외는 org.springframework.http.converter.HttpMessageNotWritableException : JSON을 쓸 수 없음 : 무한 재귀 (StackOverflowError) (참조 체인을 통해 : com.service.pegazo.bo.RequestMessageProfessional [ "requestMessage"]-> com.service.pegazo. bo.RequestMessage [ "requestMessageProfessionals"]


1
"답변 번호 8"이 없습니다. 참조 된 답변의 작성자 이름을 제공하는 것이 좋습니다. 여기에 게시 한 텍스트를 읽을 수 없습니다. 답변이 게시되는 방식을 확인하고 깔끔하게 배치 해보세요. 마지막으로 이것이 원래 질문에 어떻게 대답하는지 이해하지 못합니다. 답변을 설명하려면 더 많은 세부 정보를 추가하세요.
AdrianHHH 2015

-11

예를 들어 ProductBean에는 serialBean이 있습니다. 매핑은 양방향 관계입니다. 이제를 사용하려고하면 gson.toJson()순환 참조로 끝납니다. 이러한 문제를 방지하려면 다음 단계를 따르십시오.

  1. 데이터 소스에서 결과를 검색합니다.
  2. 목록을 반복하고 serialBean이 null이 아닌지 확인한 다음
  3. 세트 productBean.serialBean.productBean = null;
  4. 그런 다음 사용하십시오 gson.toJson();

문제가 해결되어야합니다

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.