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 2015

예외는 근본 원인을 잃고 로그를 시작합니다 JsonWriter.java:473). Gson stackoverflow의 근본 원인을 식별하는 방법
Siddharth

답변:


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 개체의 일부가 될 수 있습니다.
니므롯

하지만 그게 문제일까요? 'Collection <BomModule> modules'은 컬렉션 일 뿐이며 gson이 간단한 배열을 만들 수 있어야한다고 생각합니다.
nimrod

@dooonot : 컬렉션의 개체가 부모 개체를 참조합니까?
SLaks

내가 당신을 옳게했는지는 모르겠지만 그렇습니다. 위의 업데이트 된 질문을 참조하십시오.
nimrod

@dooonot : 내가 생각했듯이 부모 및 자식 컬렉션을 직렬화 할 때 무한 루프에 빠집니다. 어떤 종류의 JSON을 작성할 것으로 예상합니까?
SLaks

29

Log4J 로거를 클래스 속성으로 사용할 때 다음과 같은 문제가 발생했습니다.

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

이것은 로거를 만들 static거나 단순히 실제 기능으로 이동 하여 해결할 수 있습니다 .


4
절대적으로 큰 캐치. 클래스에 대한 자체 참조는 분명히 GSON이 전혀 좋아하지 않습니다. 많은 두통을 구했습니다! +1
christopher 2014

1
그것을 해결하는 또 다른 방법은 필드에 과도 수정자를 추가하는 것입니다
gawi

로거는 대부분 정적이어야합니다. 그렇지 않으면 각 객체 생성에 대해 해당 Logger 인스턴스를 가져 오는 비용이 발생하며 이는 아마도 원하는 것이 아닐 것입니다. (비용은 사소한 없습니다)
stolsvik

26

Realm을 사용 중이고이 오류가 발생하고 문제를 일으키는 객체가 RealmObject를 확장 realm.copyFromRealm(myObject)하는 경우 직렬화를 위해 GSON으로 전달하기 전에 모든 Realm 바인딩없이 복사본을 만드는 것을 잊지 마십시오 .

복사되는 객체 중 하나에 대해서만 이것을 놓쳤습니다. 스택 추적이 객체 클래스 / 유형의 이름을 지정하지 않기 때문에 깨달았습니다. 문제는 순환 참조로 인해 발생하지만 RealmObject 기본 클래스의 어딘가에 순환 참조이므로 자신의 하위 클래스가 아니므로 발견하기가 더 어렵습니다!


1
맞습니다! 제 경우에는 영역에서 직접 쿼리 된 객체 목록을 ArrayList <Image>로 변경하십시오. copyList = new ArrayList <> (); for (이미지 이미지 : 이미지) {copyList.add (realm.copyFromRealm (image)); }
Ricardo Mutti

영역을 사용하여, 그 것을 해결할 수있는 문제 문제, 감사 정확히 솔루션이었다
유 페르난데스

13

SLaks가 말했듯이 객체에 순환 참조가 있으면 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 늦게 답변을 드려서 죄송하지만 휴가 중이었습니다. 답변을 살펴보십시오 . 문제가 해결
되기를

3

이 오류는 수퍼 클래스에 로거가있을 때 흔히 발생합니다. @Zar가 이전에 제안했듯이 로거 필드에 정적 을 사용할 수 있지만 이것도 작동합니다.

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

추신은 아마 작동하고 @Expose 주석으로 여기에 대해 더 자세히 확인하십시오. https://stackoverflow.com/a/7811253/1766166에서 이에


1

나도 같은 문제를 안고있어. 제 경우에는 직렬화 된 클래스의 생성자가 다음과 같이 컨텍스트 변수를 사용하기 때문입니다.

public MetaInfo(Context context)

이 인수를 삭제하면 오류가 사라졌습니다.

public MetaInfo()

1
서비스 개체 참조를 컨텍스트로 전달할 때이 문제가 발생했습니다. gson.toJson (this)을 사용하는 클래스에서 컨텍스트 변수를 정적으로 만드는 것이 수정되었습니다.
user802467 2014

@ user802467 안드로이드 서비스를 의미합니까?
Preetam

1

편집 : 죄송합니다. 이것은 내 첫 번째 대답입니다. 조언 해 주셔서 감사합니다.

나만의 Json 변환기를 만듭니다.

내가 사용한 주요 솔루션은 각 개체 참조에 대한 부모 개체 집합을 만드는 것입니다. 하위 참조가 존재하는 상위 개체를 가리키는 경우 삭제됩니다. 그런 다음 추가 솔루션과 결합하여 엔티티 간의 양방향 관계에서 무한 루프를 피하기 위해 참조 시간을 제한합니다.

내 설명이 너무 좋지 않습니다. 도움이 되길 바랍니다.

이것은 Java 커뮤니티에 대한 첫 번째 기여입니다 (문제에 대한 해결책). 당신은 그것을 확인할 수 있습니다;) README.md 파일이 있습니다 https://github.com/trannamtrung1st/TSON


2
솔루션에 대한 링크는 환영하지만 답변이없는 경우에도 유용한 지 확인하십시오 . 링크 주변에 컨텍스트를 추가 하여 동료 사용자가 그것이 무엇이고 왜 존재하는지 알 수 있도록 한 다음 페이지에서 가장 관련성이 높은 부분을 인용하십시오. 대상 페이지를 사용할 수없는 경우 다시 연결합니다. 링크에 불과한 답변은 삭제 될 수 있습니다.
Paul Roub

2
자기 홍보 자신의 도서관이나 튜토리얼에 연결하는 것만으로는 좋은 답이 아닙니다. 여기에 연결하고, 문제를 해결하는 이유를 설명하고,이를 수행하는 방법에 대한 코드를 제공하고, 작성한 사실을 부인하면 더 나은 답을 얻을 수 있습니다. 참조 : "좋은"자기 프로모션을 의미하는 것은 무엇입니까?
Shree

정말 고마워. 내 대답을 수정했습니다. D : 괜찮을 것입니다 희망
트란 남 트룽

다른 댓글 작성자가 말한 것과 유사하게 게시물에서 코드의 가장 중요한 부분을 표시하는 것이 좋습니다. 또한 답변에 잘못이있어 사과 할 필요가 없습니다.
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

값을 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알 수 있습니다 .gsonEntity

하자 FooBar두 개의 엔티티 수 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_idFoo직렬화되고 순환 참조 문제를 일으키는 엔티티 를 나타내는 데 사용됩니다 . 이제 어댑터 Foo를 사용할 때 BarID 만 가져 와서 다시 직렬화되지 않습니다 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우리가 보여 BarCAN 트리거를 ManyToOne발생시키지 않고 Foo트리거OneToMany 다시 ...

답변은 개인적인 경험을 기반으로하므로 자유롭게 의견을 말하고, 내가 틀렸다는 것을 증명하거나, 실수를 수정하거나, 답변을 확장 할 수 있습니다. 어쨌든 누군가이 답변이 유용하다고 생각하기를 바랍니다.


핸들 직렬화 proecss 자체에 대한 어댑터를 정의하는 것은 순환 종속성을 처리하는 또 다른 방법입니다. 어댑터를 작성하는 대신이를 방지 할 수있는 다른 주석이 있지만 옵션이 있습니다.
Sariq Shaikh
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.