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;
SLaks
источник
Да, объект 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();

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

ffonz
источник
Вы знаете, есть ли аннотация, которая делает противоположное этому? Есть примерно 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 решило проблему.

Камалаканнан V
источник
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 повторного ...

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

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