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.
Крістоф Руссі,

Виняток втрачає першопричину і запускає журнал 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.
німрод

Але чи це проблема? 'Колекція <BomModule> модулі' - це лише колекція, і я думаю, що gson повинен мати можливість створити з неї простий масив?
німрод

@dooonot: Чи посилається будь-який з об'єктів у колекції на свій батьківський об'єкт?
Слакс

Я не впевнений, що я вас правильно зрозумів, але так. Будь ласка, дивіться оновлене питання вище.
німрод

@dooonot: Як я підозрював, він входить у нескінченний цикл при серіалізації батьківської та дочірньої колекцій. Який JSON ви плануєте написати?
Слакс

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 <> (); for (Зображення зображення: images) {copyList.add (realm.copyFromRealm (image)); }
Рікардо Мутті,

Використовуючи сферу, саме це рішення вирішило проблему, дякую
Джуд Фернандес,

13

Як сказав Слакс, StackOverflowError трапляється, якщо у вашому об'єкті є кругові посилання.

Щоб виправити це, ви можете використовувати TypeAdapter для свого об'єкта.

Наприклад, якщо вам потрібно лише генерувати рядок з вашого об'єкта, ви можете скористатися адаптером таким чином:

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,

@ jDub9 Вибачте за пізню відповідь, але я був у відпустці. Погляньте на цю відповідь. Сподіваюся, це вирішить вашу проблему
ffonz

3

Ця помилка є поширеною, коли у вашому супер класі є реєстратор. Як @Zar пропонував раніше, ви можете використовувати статику для вашого поля реєстратора, але це також працює:

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?
Претам

1

Редагувати: Вибачте за моє погане, це моя перша відповідь. Дякуємо за поради.

Я створюю власний Json Converter

Основне рішення, яке я використав, - це створення батьківського набору об’єктів для кожного посилання на об’єкт. Якщо під-посилання вказує на існуючий батьківський об'єкт, його буде відкинуто. Потім я поєднуюсь із додатковим рішенням, обмежуючи контрольний час, щоб уникнути інфінітивного циклу у двонаправлених відносинах між сутностями.

Мій опис не дуже хороший, сподіваюся, він допоможе вам, хлопці.

Це мій перший внесок у спільноту Java (вирішення вашої проблеми). Ви можете це перевірити;) Існує файл README.md https://github.com/trannamtrung1st/TSON


2
Посилання на рішення вітається, але будь ласка, переконайтесь, що ваша відповідь корисна без нього: додайте контекст навколо посилання, щоб ваші однодумці мали певне уявлення, що це таке і чому воно є, а потім цитуйте найбільш релевантну частину сторінки, яку ви “ повторне посилання на випадок, якщо цільова сторінка буде недоступна. Відповіді, які є лише кількома посиланнями, можуть бути видалені.
Paul Roub,

2
Самореклама Просто посилання на власну бібліотеку чи навчальний посібник не є гарною відповіддю. Посилання на нього, пояснення, чому це вирішує проблему, надання коду, як це зробити, і заперечення того, що ви його написали, дає кращу відповідь. Дивіться: Що означає «хороша» самореклама?
Шрі

Дуже дякую. Я мав редагувати свою відповідь. Сподіваюся, це буде добре: D
Trần Nam Trung

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

0

В Android переповнення стеку gson виявилося декларацією обробника. Перемістив його до класу, який не десеріалізований.

На основі рекомендацій Zar, я зробив обробник статичним, коли це сталося в іншому розділі коду. Робота статичного обробника також спрацювала.


0

BomItemпосилається на BOMModule( Collection<BomModule> modules), а BOMModuleпосилається на BOMItem( Collection<BomItem> items). Бібліотека Gson не любить кругові посилання. Видаліть цю кругову залежність зі свого класу. Раніше я теж стикався з такою ж проблемою з gson lib.


0

У мене ця проблема виникає у мене, коли я ставив:

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

в моєму об'єкті ... що було цілком зрозуміло після години або близько того налагодження!



0

Уникайте зайвих обхідних шляхів, наприклад, встановлення значень для нуля або перехідних полів. Правильний спосіб зробити це - анотувати одне з полів @Expose, а потім сказати Gson серіалізувати лише поля з анотацією:

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

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

0

У мене була подібна проблема, коли у класі була змінна InputStream, яку мені насправді не потрібно було зберігати. Отже, зміна його на Перехідний вирішила проблему.


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.