gson.toJson () выбрасывает StackOverflowError


87

Я хотел бы сгенерировать строку JSON из моего объекта:

Gson gson = new Gson();
String json = gson.toJson(item);

Каждый раз, когда я пытаюсь это сделать, я получаю такую ​​ошибку:

14:46:40,236 ERROR [[BomItemToJSON]] Servlet.service() for servlet BomItemToJSON threw exception
java.lang.StackOverflowError
    at com.google.gson.stream.JsonWriter.string(JsonWriter.java:473)
    at com.google.gson.stream.JsonWriter.writeDeferredName(JsonWriter.java:347)
    at com.google.gson.stream.JsonWriter.value(JsonWriter.java:440)
    at com.google.gson.internal.bind.TypeAdapters$7.write(TypeAdapters.java:235)
    at com.google.gson.internal.bind.TypeAdapters$7.write(TypeAdapters.java:220)
    at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:68)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.write(ReflectiveTypeAdapterFactory.java:89)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.write(ReflectiveTypeAdapterFactory.java:200)
    at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:68)
    at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.write(CollectionTypeAdapterFactory.java:96)
    at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.write(CollectionTypeAdapterFactory.java:60)
    at com.google.gson.Gson$FutureTypeAdapter.write(Gson.java:843)

Это атрибуты моего класса BomItem :

private int itemId;
private Collection<BomModule> modules;
private boolean deprecated;
private String partNumber;
private String description; //LOB
private int quantity;
private String unitPriceDollar;
private String unitPriceEuro;
private String discount; 
private String totalDollar;
private String totalEuro;
private String itemClass;
private String itemType;
private String vendor;
private Calendar listPriceDate;
private String unitWeight;
private String unitAveragePower;
private String unitMaxHeatDissipation;
private String unitRackSpace;

Атрибуты моего указанного класса BomModule :

private int moduleId;
private String moduleName;
private boolean isRootModule;
private Collection<BomModule> parentModules;
private Collection<BomModule> subModules;
private Collection<BomItem> items;
private int quantity;

Есть идеи, что вызывает эту ошибку? Как я могу это исправить?


Может случиться, если вы поместите экземпляр объекта внутрь себя где-то внутри gson.
— Christophe Roussy

Исключение теряет основную причину и запускает журнал JsonWriter.java:473), как определить основную причину Gson stackoverflow
— Сиддхарт,

Ответы:


86

Проблема в том, что у вас есть круговая ссылка.

В BomModuleклассе вы имеете в виду :

private Collection<BomModule> parentModules;
private Collection<BomModule> subModules;

Эта ссылка на себя BomModule, очевидно, совершенно не понравилась GSON.

Обходной путь - просто настроить модули, nullчтобы избежать рекурсивного цикла. Таким образом я могу избежать исключения StackOverFlow-Exception.

item.setModules(null);

Или отметьте поля, которые вы не хотите отображать в сериализованном json, используя transientключевое слово, например:

private transient Collection<BomModule> parentModules;
private transient Collection<BomModule> subModules;

Да, объект BomModule может быть частью другого объекта BomModule.
— nimrod

Но разве это проблема? «Модули коллекции <BomModule>» - это всего лишь коллекция, и я думаю, что gson должен иметь возможность сделать из нее простой массив?
— nimrod

@dooonot: ссылается ли какой-либо из объектов в коллекции на их родительский объект?
— SLaks

Я не уверен, что правильно понял, но да. См. Обновленный вопрос выше.
— nimrod

@dooonot: как я и подозревал, при сериализации родительской и дочерней коллекций происходит бесконечный цикл. Какой JSON вы ожидаете от него написать?
— SLaks

29

У меня была эта проблема, когда у меня был логгер Log4J в качестве свойства класса, например:

private Logger logger = Logger.getLogger(Foo.class);

Это можно решить, сделав регистратор staticили просто переместив его в фактическую функцию (ы).


4
Абсолютно отличный улов. Эта ссылка на класс явно не понравилась GSON. Избавил меня от многих головных болей! +1
— кристофер

1
другой способ решить эту проблему - добавить в поле модификатор переходного процесса
— gawi

регистратор в основном должен быть статическим. В противном случае вы будете нести расходы на получение этого экземпляра Logger для каждого создания объекта, что, вероятно, не то, что вам нужно. (Стоимость не тривиальна)
— stolsvik

26

Если вы используете Realm и получаете эту ошибку, а объект, вызывающий проблему, расширяет RealmObject, не забудьте realm.copyFromRealm(myObject)создать копию без всех привязок Realm, прежде чем перейти к GSON для сериализации.

Я пропустил это только для одного из множества копируемых объектов ... мне потребовалось много времени, чтобы понять, поскольку трассировка стека не называет класс / тип объекта. Дело в том, что проблема вызвана циклической ссылкой, но это циклическая ссылка где-то в базовом классе RealmObject, а не в вашем собственном подклассе, что затрудняет обнаружение!


1
Это правильно! В моем случае измените мой список объектов, запрашиваемый непосредственно из области, на ArrayList <Image> copyList = new ArrayList <> (); для (Изображение изображения: изображения) {copyList.add (realm.copyFromRealm (изображение)); }
— Рикардо Мутти

Использование realm - именно то решение, которое решает проблему, спасибо
— Джуд Фернандес

13

Как сказал SLaks, StackOverflowError возникает, если у вас есть круговая ссылка в вашем объекте.

Чтобы исправить это, вы можете использовать TypeAdapter для своего объекта.

Например, если вам нужно только сгенерировать String из вашего объекта, вы можете использовать адаптер следующим образом:

class MyTypeAdapter<T> extends TypeAdapter<T> {
    public T read(JsonReader reader) throws IOException {
        return null;
    }

    public void write(JsonWriter writer, T obj) throws IOException {
        if (obj == null) {
            writer.nullValue();
            return;
        }
        writer.value(obj.toString());
    }
}

и зарегистрируйте его так:

Gson gson = new GsonBuilder()
               .registerTypeAdapter(BomItem.class, new MyTypeAdapter<BomItem>())
               .create();

или вот так, если у вас есть интерфейс и вы хотите использовать адаптер для всех его подклассов:

Gson gson = new GsonBuilder()
               .registerTypeHierarchyAdapter(BomItemInterface.class, new MyTypeAdapter<BomItemInterface>())
               .create();

9

Мой ответ немного запоздал, но я думаю, что у этого вопроса пока нет хорошего решения. Я нашел это изначально здесь .

С Gson вы можете отметить поля , которые вы действительно хотите быть включены в JSON с @Exposeтак:

@Expose
String myString;  // will be serialized as myString

и создайте объект gson с помощью:

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

Циркулярные ссылки вы просто не выставляете. Это помогло мне!


Вы знаете, есть ли аннотация, которая делает противоположное этому? Есть примерно 4 поля, которые мне нужно игнорировать, и более 30, которые мне нужно включить.
— jDub9 06

@ jDub9 Извините за поздний ответ, но я был в отпуске. Взгляните на этот ответ. Надеюсь, это решит вашу проблему
— ffonz

3

Эта ошибка часто встречается, когда у вас есть регистратор в вашем суперклассе. Как ранее предлагал @Zar, вы можете использовать static для поля своего регистратора, но это также работает:

protected final transient Logger logger = Logger.getLogger(this.getClass());

PS, вероятно, это сработает, и с аннотацией @Expose проверьте больше об этом здесь: https://stackoverflow.com/a/7811253/1766166


1

У меня такая же проблема. В моем случае причина заключалась в том, что конструктор моего сериализованного класса принимает переменную контекста, например:

public MetaInfo(Context context)

Когда я удаляю этот аргумент, ошибка исчезла.

public MetaInfo()

1
Я столкнулся с этой проблемой при передаче ссылки на объект службы в качестве контекста. Исправление заключалось в том, чтобы сделать переменную контекста статической в ​​классе, который использует gson.toJson (this).
— user802467 04

@ user802467 вы имеете в виду сервис android?
— Preetam

1

Изменить: извините за мою ошибку, это мой первый ответ. Спасибо за советы.

Я создаю свой собственный конвертер Json

Основное решение, которое я использовал, - создать набор родительских объектов для каждой ссылки на объект. Если вспомогательная ссылка указывает на существующий родительский объект, она будет отброшена. Затем я комбинирую с дополнительным решением, ограничивая время обращения, чтобы избежать бесконечного цикла в двунаправленных отношениях между объектами.

Мое описание не очень хорошее, надеюсь, оно вам поможет.

Это мой первый вклад в сообщество Java (решение вашей проблемы). Можете проверить;) Есть файл README.md https://github.com/trannamtrung1st/TSON


2
Ссылка на решение приветствуется, но убедитесь, что ваш ответ полезен и без нее: добавьте контекст вокруг ссылки, чтобы ваши друзья-пользователи имели некоторое представление, что это такое и почему оно есть, а затем процитируйте наиболее релевантную часть страницы, которую вы повторная ссылка на, если целевая страница недоступна. Ответы, которые представляют собой не более чем ссылку, могут быть удалены.
— Paul Roub

2
Самореклама Просто ссылка на вашу собственную библиотеку или учебное пособие - не лучший ответ. Ссылки на него, объяснение, почему он решает проблему, предоставление кода о том, как это сделать, и отказ от того, что вы его написали, дают лучший ответ. См .: Что означает «хорошая» самореклама?
— Shree

Спасибо. Я отредактировал свой ответ. Надеюсь, все будет хорошо: D
— Trần Nam Trung

Подобно тому, что говорили другие комментаторы, желательно, чтобы вы показывали наиболее важные части вашего кода в своем сообщении. Кроме того, не нужно извиняться за ошибки в своем ответе.
— 0xCursor

0

В Android переполнение стека gson оказалось объявлением обработчика. Переместил его в класс, который не десериализуется.

По рекомендации Зара я сделал обработчик статическим, когда это произошло в другом разделе кода. Сделать обработчик статическим тоже сработало.


0

BomItemотносится к BOMModule( Collection<BomModule> modules) и BOMModuleотносится к BOMItem( Collection<BomItem> items). Библиотека Gson не любит циклические ссылки. Удалите эту циклическую зависимость из своего класса. Я тоже сталкивался с той же проблемой в прошлом с gson lib.


0

У меня возникла эта проблема, когда я поставил:

Logger logger = Logger.getLogger( this.getClass().getName() );

в моем объекте ... что имело смысл после часа отладки!



0

Избегайте ненужных обходных приемов, таких как установка значений на null или создание временных полей. Правильный способ сделать это - аннотировать одно из полей с помощью @Expose, а затем указать Gson сериализовать только поля с аннотацией:

private Collection<BomModule> parentModules;
@Expose
private Collection<BomModule> subModules;

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

0

У меня была аналогичная проблема, когда в классе была переменная InputStream, которую мне действительно не нужно было сохранять. Следовательно, изменение его на Transient решило проблему.


0

После некоторой борьбы с этой проблемой я считаю, что у меня есть решение. Проблема в неразрешенных двунаправленных соединениях и в том, как представлять соединения при их сериализации. Способ исправить такое поведение - «сказать», gsonкак сериализовать объекты. Для этого мы используемAdapters .

Используя, Adaptersмы можем сказать, gsonкак сериализовать каждое свойство из вашегоEntity класса, а также какие свойства сериализовать.

Позвольте Fooи Barбыть двумя объектами, где Fooимеет OneToManyотношение к Barи Barимеет ManyToOneотношение к Foo. Мы определяем Barадаптер таким образом, чтобы при gsonсериализации Barопределить, как сериализовать Fooс точки зрения Barциклических ссылок, будет невозможно.

public class BarAdapter implements JsonSerializer<Bar> {
    @Override
    public JsonElement serialize(Bar bar, Type typeOfSrc, JsonSerializationContext context) {
        JsonObject jsonObject = new JsonObject();
        jsonObject.addProperty("id", bar.getId());
        jsonObject.addProperty("name", bar.getName());
        jsonObject.addProperty("foo_id", bar.getFoo().getId());
        return jsonObject;
    }
}

Здесь foo_idиспользуется для представления Fooобъекта, который будет сериализован и вызовет нашу проблему с циклическими ссылками. Теперь, когда мы используем адаптер, Fooон не будет снова сериализован, Barтолько его идентификатор будет взят и вставлен JSON. Теперь у нас есть Barадаптер, и мы можем использовать его для сериализации Foo. Вот идея:

public String getSomething() {
    //getRelevantFoos() is some method that fetches foos from database, and puts them in list
    List<Foo> fooList = getRelevantFoos();

    GsonBuilder gsonBuilder = new GsonBuilder();
    gsonBuilder.registerTypeAdapter(Bar.class, new BarAdapter());
    Gson gson = gsonBuilder.create();

    String jsonResponse = gson.toJson(fooList);
    return jsonResponse;
}

Еще одна вещь, которую нужно уточнить, foo_idне является обязательной, и ее можно пропустить. Целью адаптера в этом примере является сериализация, Barи foo_idмы показали, что он Barможет запускаться, ManyToOneне вызывая FooзапускаOneToMany повторного ...

Ответ основан на личном опыте, поэтому не стесняйтесь комментировать, чтобы доказать свою неправоту, исправить ошибки или расширить ответ. В любом случае я надеюсь, что кто-то сочтет этот ответ полезным.


Определение адаптера для процесса сериализации дескрипторов - еще один способ обработки циклической зависимости. У вас есть возможность для этого, хотя есть и другие аннотации, которые могут предотвратить это вместо написания адаптеров.
— Сарик Шейх
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.