developer tip

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

optionbox 2020. 10. 22. 07:55
반응형

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


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


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

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


이 기능을 사용하여 이러한 종류의 문제를 처리하기 위해 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 클래스 살펴보기


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

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

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


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

  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);

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

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


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

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

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


Javascript를 사용하는 경우 메서드 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);
}

Later, you can easily recreate the actual object with the cyclic references by parsing the serialized data and modifying the next property to point to the actual object if it's using a named reference with a @ like in this example.


This is how i finally solved it in my case. This works at least with 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;
} 

This error can appened when you have two objects :

class object1{
    private object2 o2;
}

class object2{
    private object1 o1;
}

With using GSon for serialization, i have got this error :

java.lang.IllegalStateException: circular reference error

Offending field: o1

To solved this, just add key word transient :

class object1{
    private object2 o2;
}

class object2{
    transient private object1 o1;
}

As you can see here : Why does Java have transient fields?

The transient keyword in Java is used to indicate that a field should not be serialized.


Jackson provides JsonIdentityInfo annotation to prevent circular references. You can check the tutorial here.


the answer number 8 is the better, i think so if you know what field is throwing a error the you only set the fild in null and solved.

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); // **
        }
    }

To make the code readable a big comment is moved from the comment // ** to below.

java.lang.StackOverflowError [Request processing failed; nested exception is org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: Infinite recursion (StackOverflowError) (through reference chain: com.service.pegazo.bo.RequestMessageProfessional["requestMessage"]->com.service.pegazo.bo.RequestMessage["requestMessageProfessionals"]


For example, ProductBean has got serialBean. The mapping would be bi-directional relationship. If we now try to use gson.toJson(), it will end up with circular reference. In order to avoid that problem, you can follow these steps:

  1. Retrieve the results from datasource.
  2. Iterate the list and make sure the serialBean is not null, and then
  3. Set productBean.serialBean.productBean = null;
  4. Then try to use gson.toJson();

That should solve the problem

참고URL : https://stackoverflow.com/questions/3340485/how-to-solve-circular-reference-in-json-serializer-caused-by-hibernate-bidirecti

반응형