Как решить циклическую ссылку в json сериализаторе, вызванную двунаправленным отображением гибернации? - PullRequest
74 голосов
/ 27 июля 2010

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

Ответы [ 12 ]

45 голосов
/ 27 июля 2010

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

Исключая поля из сериализации и десериализации

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

public class A implements Serializable {

    private 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 класс

33 голосов
/ 29 июля 2010

Джексон 1.6 (выпущен в сентябре 2010 г.) имеет специальную поддержку на основе аннотаций для обработки таких родительских / дочерних связей, см. http://wiki.fasterxml.com/JacksonFeatureBiDirReferences. ( Wayback Snapshot )

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

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

12 голосов
/ 17 апреля 2011

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

  1. Создайте класс аннотаций для использования в полях, которые вы хотитекак исключено
  2. Определите класс, который реализует интерфейс Google ExclusionStrategy
  3. Создайте простой метод для генерации объекта GSON с помощью GsonBuilder (аналогично объяснению Артура)
  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;
    }

}

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);
10 голосов
/ 27 июля 2010

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

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

3 голосов
/ 06 июня 2017

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

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

2 голосов
/ 17 ноября 2014

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

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

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

1 голос
/ 17 ноября 2016

Вот как я наконец решил это в моем случае. Это работает по крайней мере с 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;
} 
1 голос
/ 31 марта 2014

Если вы используете 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, чтобы оно указывало на фактический объект, если он использует именованную ссылку с @, как в этом примере.

0 голосов
/ 18 июня 2018

Джексон предоставляет JsonIdentityInfo аннотацию для предотвращения циклических ссылок. Вы можете проверить учебник здесь .

0 голосов
/ 19 июля 2016

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

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 используется для указания того, что поле не должно быть сериализовано.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...