Я пишу сериализатор, чтобы сериализовать POJO на JSON, но застрял в круговой справочной проблеме. В hibernate двунаправленное отношение "один ко многим" родительские ссылки обращаются к родительскому и дочернему объектам обратно к родителям, и здесь мой сериализатор умирает. (см. примерный код ниже)
Как нарушить этот цикл? Можем ли мы получить дерево владельца объекта, чтобы увидеть, существует ли сам объект в своей собственной иерархии владельца? Любой другой способ найти, будет ли ссылка циркулярной? или любую другую идею для решения этой проблемы?
Как решить круговую ссылку в json serializer, вызванную двунаправленным сопоставлением спящего режима?
Ответ 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();
Это должно решить проблему