Как решить круговую ссылку в json serializer, вызванную двунаправленным сопоставлением спящего режима?

Я пишу сериализатор, чтобы сериализовать POJO на JSON, но застрял в круговой справочной проблеме. В hibernate двунаправленное отношение "один ко многим" родительские ссылки обращаются к родительскому и дочернему объектам обратно к родителям, и здесь мой сериализатор умирает. (см. примерный код ниже)
Как нарушить этот цикл? Можем ли мы получить дерево владельца объекта, чтобы увидеть, существует ли сам объект в своей собственной иерархии владельца? Любой другой способ найти, будет ли ссылка циркулярной? или любую другую идею для решения этой проблемы?

Ответ 1

Можно ли представить двунаправленную связь в JSON? Некоторые форматы данных не подходят для некоторых типов моделирования данных.

Один из способов обращения с циклами при работе с обходными графами объектов - следить за тем, какие объекты вы видели до сих пор (используя сравнения идентичности), чтобы предотвратить переход от бесконечного цикла.

Ответ 2

Я полагаюсь на Google JSON Для решения этой проблемы с помощью функции

Исключение полей от сериализации и десериализации

Предположим, что двунаправленное соотношение между классами A и B следующим образом

public class A implements Serializable {

    private B b;

}

И B

public class B implements Serializable {

    private A a;

}

Теперь используйте GsonBuilder. Чтобы получить пользовательский объект Gson следующим образом (обратите внимание на метод 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 класс

Ответ 3

Jackson 1.6 (выпущен в сентябре 2010 г.) имеет определенную поддержку на основе аннотаций для обработки такой привязки parent/child, см. http://wiki.fasterxml.com/JacksonFeatureBiDirReferences. (Обратный снимок)

Вы можете, конечно, уже исключить сериализацию родительской ссылки, уже использующую большинство пакетов обработки JSON (джексон, gson и flex-json хотя бы поддерживают его), но реальная уловка заключается в том, как десериализовать ее обратно (воссоздать родительскую ссылку), а не просто обрабатывать сериализацию. Хотя кажется, что сейчас просто исключение может сработать для вас.

EDIT (апрель 2012): Jackson 2.0 теперь поддерживает true идентификационные ссылки (Обратный снимок), поэтому вы также можете решить эту проблему.

Ответ 4

При решении этой проблемы я применил следующий подход (стандартизацию процесса в моем приложении, делая код понятным и многократным):

  • Создайте класс аннотаций, который будет использоваться в полях, которые вы хотели бы исключить.
  • Определите класс, который реализует интерфейс Google ExclusionStrategy
  • Создайте простой метод для создания объекта GSON с помощью GsonBuilder (аналогично объяснению Артура)
  • Аннотировать поля, которые необходимо исключить при необходимости
  • Применить правила сериализации к объекту com.google.gson.Gson
  • Сериализовать свой объект

Здесь код:

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

}

3)

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

Ответ 5

Если вы используете Jackon для сериализации, просто примените @JsonBackReference к вашему двунаправленному сопоставлению Он решает круговую справочную проблему.

Примечание: @JsonBackReference используется для решения бесконечной рекурсии (StackOverflowError)

Ответ 6

Если вы используете 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);
}

Позже вы можете легко воссоздать фактический объект с циклическими ссылками, проанализировав сериализованные данные и изменив свойство next, чтобы указать на фактический объект, если он использует именованную ссылку с @, как в этом примере.

Ответ 7

Используется решение, подобное Артуру, но вместо setExclusionStrategies я использовал

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

и используется @Expose аннотация gson для полей, которые мне нужны в json, другие поля исключены.

Ответ 8

Вот как я, наконец, решил это в моем случае. Это работает, по крайней мере, с Гсеном и Джексоном.

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

Ответ 9

Эта ошибка может быть добавлена, если у вас есть два объекта:

class object1{
    private object2 o2;
}

class object2{
    private object1 o1;
}

С использованием GSon для сериализации я получил эту ошибку:

java.lang.IllegalStateException: circular reference error

Offending field: o1

Чтобы решить эту проблему, просто добавьте ключевое слово переходное:

class object1{
    private object2 o2;
}

class object2{
    transient private object1 o1;
}

Как вы можете видеть здесь: Почему Java имеет переходные поля?

Ключевое слово переходного процесса в Java используется для указания того, что поле не должно быть сериализовано.

Ответ 10

номер ответа 8 лучше, я думаю, что если вы знаете, какое поле бросает ошибку, вы только устанавливаете fild в 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" ]

Ответ 11

Например, ProductBean имеет serialBean. Отображение будет двунаправленным. Если мы теперь попытаемся использовать gson.toJson(), в итоге получится круговая ссылка. Чтобы избежать этой проблемы, вы можете выполнить следующие действия:

  • Получить результаты из источника данных.
  • Итерируйте список и убедитесь, что serialBean не равен null, а затем
  • Установить productBean.serialBean.productBean = null;
  • Затем попробуйте использовать gson.toJson();

Это должно решить проблему