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
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 อื่นได้
nimrod

แต่นั่นเป็นปัญหาหรือไม่? 'คอลเลกชัน <BomModule> โมดูล' เป็นเพียงคอลเลกชันเท่านั้นและฉันคิดว่า gson ควรจะสร้างอาร์เรย์แบบธรรมดาออกมาได้หรือไม่?
nimrod

@dooonot: วัตถุใด ๆ ในคอลเลกชันอ้างอิงถึงวัตถุแม่หรือไม่?
SLaks

ฉันไม่แน่ใจว่าฉันเข้าใจคุณถูกมั้ย แต่ใช่ โปรดดูคำถามที่อัปเดตด้านบน
nimrod

@dooonot: อย่างที่ฉันสงสัยมันจะเข้าสู่วงวนที่ไม่มีที่สิ้นสุดเมื่อทำให้คอลเลกชันแม่และลูกเป็นอนุกรม คุณคาดหวังให้มันเขียน JSON แบบไหน?
SLaks

30

ฉันมีปัญหานี้เมื่อฉันมี Log4J logger เป็นคุณสมบัติคลาสเช่น:

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

สิ่งนี้สามารถแก้ไขได้โดยการสร้างคนตัดไม้staticหรือเพียงแค่ย้ายไปไว้ในฟังก์ชันจริง


4
จับที่ยอดเยี่ยมอย่างแน่นอน การอ้างอิงตัวเองไปยังชั้นเรียนนั้นไม่ชอบโดย GSON เลย ช่วยให้ฉันปวดหัวมาก! +1
christopher

1
อีกวิธีหนึ่งในการแก้ปัญหาคือการเพิ่มตัวปรับชั่วคราวลงในฟิลด์
gawi

คนตัดไม้ส่วนใหญ่ควรเป็นแบบคงที่ มิฉะนั้นคุณจะต้องเสียค่าใช้จ่ายในการรับอินสแตนซ์ Logger สำหรับการสร้างอ็อบเจ็กต์แต่ละครั้งซึ่งอาจไม่ใช่สิ่งที่คุณต้องการ (ค่าใช้จ่ายไม่สำคัญ)
stolsvik

26

หากคุณใช้ Realm และคุณได้รับข้อผิดพลาดนี้และวัตถุที่ทำให้เกิดปัญหาขยาย RealmObject อย่าลืมrealm.copyFromRealm(myObject)สร้างสำเนาโดยไม่มีการผูก Realm ทั้งหมดก่อนที่จะส่งผ่านไปยัง GSON สำหรับการทำให้เป็นอนุกรม

ฉันพลาดการทำสิ่งนี้ไปเพียงชิ้นเดียวในกลุ่มของวัตถุที่ถูกคัดลอก ... ปัญหาคือปัญหาเกิดจากการอ้างอิงแบบวงกลม แต่เป็นการอ้างอิงแบบวงกลมที่ใดที่หนึ่งในคลาสฐาน RealmObject ไม่ใช่คลาสย่อยของคุณเองซึ่งทำให้ยากต่อการมองเห็น!


1
ถูกต้อง! ในกรณีของฉันเปลี่ยนรายการวัตถุของฉันที่สอบถามโดยตรงจาก realm เป็น ArrayList <Image> copyList = new ArrayList <> (); สำหรับ (Image image: images) {copyList.add (realm.copyFromRealm (image)); }
Ricardo Mutti

การใช้ดินแดนนั่นเป็นทางออกที่ช่วยแก้ปัญหาได้อย่างแท้จริงขอบคุณ
Jude Fernandes

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 ขออภัยสำหรับคำตอบที่ล่าช้า แต่ฉันได้รับในช่วงวันหยุด มีลักษณะที่นี้คำตอบ หวังว่ามันจะช่วยแก้ปัญหาของคุณได้นะ
ffonz

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

@ user802467 คุณหมายถึงบริการ android หรือเปล่า?
Preetam

1

แก้ไข: ขออภัยที่ไม่ดีนี่เป็นคำตอบแรกของฉัน ขอบคุณสำหรับคำแนะนำของคุณ

ฉันสร้าง Json Converter ของตัวเอง

วิธีแก้ปัญหาหลักที่ฉันใช้คือการสร้างชุดอ็อบเจ็กต์ parent สำหรับการอ้างอิงแต่ละอ็อบเจ็กต์ หากการอ้างอิงย่อยชี้ไปยังออบเจ็กต์หลักที่มีอยู่การอ้างอิงนั้นจะถูกละทิ้ง จากนั้นฉันรวมกับโซลูชันพิเศษ จำกัด เวลาอ้างอิงเพื่อหลีกเลี่ยง infinitive loop ในความสัมพันธ์แบบสองทิศทางระหว่างเอนทิตี

คำอธิบายของฉันไม่ดีเกินไปหวังว่ามันจะช่วยพวกคุณได้

นี่เป็นการมีส่วนร่วมครั้งแรกของฉันในชุมชน Java (วิธีแก้ปัญหาของคุณ) คุณสามารถตรวจสอบได้) มีไฟล์ README.md https://github.com/trannamtrung1st/TSON


2
ยินดีต้อนรับลิงก์ไปยังโซลูชัน แต่โปรดตรวจสอบให้แน่ใจว่าคำตอบของคุณมีประโยชน์หากไม่มีมัน: เพิ่มบริบทรอบ ๆ ลิงก์เพื่อให้ผู้ใช้เพื่อนของคุณมีความคิดว่ามันคืออะไรและทำไมจึงอยู่ที่นั่นจากนั้นอ้างถึงส่วนที่เกี่ยวข้องที่สุดของเพจ การเชื่อมโยงอีกครั้งในกรณีที่หน้าเป้าหมายไม่พร้อมใช้งาน คำตอบที่มากกว่าลิงก์เพียงเล็กน้อยอาจถูกลบ
Paul Roub

2
การส่งเสริมตนเองเพียงแค่เชื่อมโยงไปยังห้องสมุดหรือบทช่วยสอนของคุณเองไม่ใช่คำตอบที่ดี การเชื่อมโยงไปยังสิ่งนี้อธิบายว่าเหตุใดจึงสามารถแก้ปัญหาได้ให้รหัสเกี่ยวกับวิธีการทำเช่นนั้นและการปฏิเสธว่าคุณเขียนมันทำให้ได้คำตอบที่ดีกว่า ดู: อะไรแสดงว่าการส่งเสริมตนเอง“ ดี”?
Shree

ขอบคุณมาก. ฉันแก้ไขคำตอบของฉันแล้ว หวังว่าคงจะสบายดีนะ: D
Trần Nam Trung

เช่นเดียวกับที่ผู้แสดงความคิดเห็นคนอื่น ๆ กล่าวไว้คุณควรแสดงส่วนที่สำคัญที่สุดของโค้ดในโพสต์ของคุณ นอกจากนี้คุณไม่จำเป็นต้องขอโทษสำหรับความผิดพลาดในคำตอบของคุณ
0xCursor

0

ใน Android gson stack overflow กลายเป็นการประกาศของ Handler ย้ายไปยังชั้นเรียนที่ไม่ได้ถูก deserialized

ตามคำแนะนำของ 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เพียง ID 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อีกครั้ง ...

คำตอบขึ้นอยู่กับประสบการณ์ส่วนตัวดังนั้นอย่าลังเลที่จะแสดงความคิดเห็นเพื่อพิสูจน์ว่าฉันผิดแก้ไขข้อผิดพลาดหรือขยายคำตอบ ฉันหวังว่าใครบางคนจะพบว่าคำตอบนี้มีประโยชน์


การกำหนดอะแด็ปเตอร์สำหรับจัดการ Serialization proecss นั้นเป็นอีกวิธีหนึ่งในการจัดการการพึ่งพาแบบวนรอบ คุณมีทางเลือกสำหรับมันแม้ว่าจะมีคำอธิบายประกอบอื่น ๆ ที่สามารถป้องกันไม่ให้สิ่งนี้เกิดขึ้นแทนที่จะเขียนอะแดปเตอร์
Sariq Shaikh
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.