Как решить циклическую ссылку в JSON сериализатор, вызванной двусторонней сопоставления библиотеки Hibernate?
Я пишу сериализатор для сериализации POJO в JSON, но застрял в проблеме круговой ссылки. В hibernate двунаправленное отношение один ко многим, родительские ссылки дочерние и дочерние ссылки обратно к родителю и здесь мой сериализатор умирает. (смотрите пример кода ниже)
Как разорвать этот круг? Можем ли мы получить дерево владельца объекта, чтобы увидеть, существует ли сам объект где-то в своей собственной иерархии владельцев? Любой другой способ найти, будет ли ссылка круговой? или любая другая идея, чтобы решить эту проблему?
12 ответов:
может ли двунаправленная связь быть даже представлена в JSON? Некоторые форматы данных не подходят для некоторых типов моделирования данных.
один из методов работы с циклами при работе с графами объектов-это отслеживать, какие объекты вы видели до сих пор (используя сравнения идентичности), чтобы предотвратить переход по бесконечному циклу.
Я опираюсь на 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 класс
Джексон 1.6 (выпущен в сентябре 2010 года) имеет специальную поддержку аннотаций для обработки такой связи родитель / потомок, см. http://wiki.fasterxml.com/JacksonFeatureBiDirReferences. (Wayback Snapshot)
вы можете, конечно, уже исключить сериализацию родительской ссылки, уже используя большинство пакетов обработки JSON (jackson, gson и flex-json по крайней мере поддерживают его), но реальный трюк заключается в том, как десериализовать его обратно (воссоздать Родительский ссылка), а не просто обрабатывать сторону сериализации. Хотя звучит, как сейчас просто исключение может работать для вас.
EDIT (апрель 2012):Джексон 2.0 теперь поддерживает true ссылки (Wayback Snapshot), Так что вы можете решить это так же.
при решении этой проблемы я использовал следующий подход (стандартизация процесса в моем приложении, что делает код понятным и многоразовым):
- создайте класс аннотаций для использования в полях, которые вы хотите исключить
- определите класс, который реализует интерфейс Google ExclusionStrategy
- создайте простой метод для создания объекта GSON с помощью GsonBuilder (аналогично объяснению Артура)
- комментировать поля, которые будут исключены по мере необходимости
- примените правила сериализации к вашему com.гуглить.гсон.Объект 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);
Если вы используете Jackon для сериализации, просто примените @JsonBackReference в свой Би-directinal сопоставление Это позволит решить проблему циклических ссылок.
Примечание: @JsonBackReference используется для решения бесконечной рекурсии (StackOverflowError)
использовал решение, похожее на Артура, но вместо
setExclusionStrategiesЯGson gson = new GsonBuilder() .excludeFieldsWithoutExposeAnnotation() .create();и использовать
@Exposeаннотация gson для полей, которые мне нужны в json, другие поля исключены.
вот как я, наконец, решил это в моем случае. Это работает, по крайней мере, с 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; }
эта ошибка может возникнуть, когда у вас есть два объекта:
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 имеет переходные поля?
ключевое слово transient в Java используется для указания того, что поле не должно быть сериализовано.
Джексон предоставляет
JsonIdentityInfoаннотация для предотвращения циклических ссылок. Вы можете проверить учебник здесь.
номер ответа 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.ленг.StackOverflowError [обработка запроса не удалось; вложенные исключением является org.springframework.http.конвертер.HttpMessageNotWritableException: не удалось написать JSON: бесконечная рекурсия (StackOverflowError) (через цепочку ссылок: com.service.pegazo.bo.RequestMessageProfessional["requestMessage"]->com.service.pegazo.bo.RequestMessage["requestMessageProfessionals"]
например, ProductBean имеет serialBean. Отображение будет двунаправленным отношением. Если мы сейчас попробуем использовать
gson.toJson(), Он будет в конечном итоге с циклической ссылкой. Чтобы избежать этой проблемы, вы можете выполнить следующие действия:
- получить результаты из источника данных.
- повторите список и убедитесь, что serialBean не является null, а затем
- Set
productBean.serialBean.productBean = null;- тогда попробуйте использовать
gson.toJson();что должен решите проблему
Comments