Google Gson - ยกเลิกการลบรายการ <class> รายการวัตถุหรือไม่ (ประเภททั่วไป)


441

ฉันต้องการถ่ายโอนวัตถุรายการผ่าน Google Gson แต่ฉันไม่ทราบวิธียกเลิกการกำหนดประเภททั่วไป

สิ่งที่ฉันลองหลังจากดูที่คำตอบนี้ (คำตอบของ BalusC):

MyClass mc = new Gson().fromJson(result, new List<MyClass>(){}.getClass());

แต่จากนั้นฉันได้รับข้อผิดพลาดใน eclipse ที่พูดว่า "The new list () {} ต้องใช้วิธีนามธรรมที่สืบทอดมา ... " และถ้าฉันใช้การแก้ไขอย่างรวดเร็วฉันจะได้มอนสเตอร์มากกว่า 20 วิธี

ฉันค่อนข้างมั่นใจว่ามีวิธีแก้ปัญหาที่ง่ายกว่า แต่ฉันก็หาไม่เจอ!

แก้ไข:

ตอนนี้ฉันมี

Type listType = new TypeToken<List<MyClass>>()
                {
                }.getType();

MyClass mc = new Gson().fromJson(result, listType);

อย่างไรก็ตามฉันได้รับข้อยกเว้นต่อไปนี้ที่บรรทัด "fromJson":

java.lang.NullPointerException
at org.apache.harmony.luni.lang.reflect.ListOfTypes.length(ListOfTypes.java:47)
at org.apache.harmony.luni.lang.reflect.ImplForType.toString(ImplForType.java:83)
at java.lang.StringBuilder.append(StringBuilder.java:203)
at com.google.gson.JsonDeserializerExceptionWrapper.deserialize(JsonDeserializerExceptionWrapper.java:56)
at com.google.gson.JsonDeserializationVisitor.invokeCustomDeserializer(JsonDeserializationVisitor.java:88)
at com.google.gson.JsonDeserializationVisitor.visitUsingCustomHandler(JsonDeserializationVisitor.java:76)
at com.google.gson.ObjectNavigator.accept(ObjectNavigator.java:106)
at com.google.gson.JsonDeserializationContextDefault.fromJsonArray(JsonDeserializationContextDefault.java:64)
at com.google.gson.JsonDeserializationContextDefault.deserialize(JsonDeserializationContextDefault.java:49)
at com.google.gson.Gson.fromJson(Gson.java:568)
at com.google.gson.Gson.fromJson(Gson.java:515)
at com.google.gson.Gson.fromJson(Gson.java:484)
at com.google.gson.Gson.fromJson(Gson.java:434)

ฉันทำจับ JsonParseExceptions และ "ผล" เป็นไม่เป็นโมฆะ

ฉันตรวจสอบ listType กับดีบักเกอร์และได้รับสิ่งต่อไปนี้:

  • รายการประเภท
    • args = ListOfTypes
      • รายการ = null
      • resolveTypes = ประเภท [1]
    • loader = PathClassLoader
    • ownerType0 = null
    • ownerTypeRes = null
    • rawType = Class (java.util.ArrayList)
    • rawTypeName = "java.util.ArrayList"

ดังนั้นดูเหมือนว่าการเรียกใช้ "getClass" จะทำงานไม่ถูกต้อง ข้อเสนอแนะใด ๆ ... ?

Edit2: I'v การตรวจสอบในคู่มือการใช้ Gson มันกล่าวถึงข้อยกเว้นรันไทม์ที่ควรเกิดขึ้นในระหว่างการแยกประเภททั่วไปกับ Json ฉันทำมัน "ผิด" (ไม่แสดงด้านบน) เช่นเดียวกับในตัวอย่าง แต่ไม่ได้รับข้อยกเว้นนั้น ดังนั้นฉันจึงเปลี่ยนการจัดลำดับตามที่แนะนำในคู่มือผู้ใช้ ไม่ได้ช่วย แต่

แก้ไข 3: แก้ไขแล้วดูคำตอบของฉันด้านล่าง


1
TokenTypeคำตอบที่คุณชี้ไปใช้ คุณลองแบบนั้นหรือไม่?
Nishant

เพิ่งได้คำใบ้เดียวกับคำตอบ ครั้งต่อไปฉันจะยกตัวอย่างให้ดูใกล้ขึ้น ;)
แมงกะพรุน

คุณสามารถลองใช้งานรายการในประเภทโทเค็นได้หรือไม่ เนื่องจากประเภทดิบของคุณคือรายการอาร์เรย์คุณควรลองรายการอาร์เรย์
uncaught_exceptions

คำตอบ:


954

วิธีการ deserialize การรวบรวมทั่วไป:

import java.lang.reflect.Type;
import com.google.gson.reflect.TypeToken;

...

Type listType = new TypeToken<ArrayList<YourClass>>(){}.getType();
List<YourClass> yourClassList = new Gson().fromJson(jsonArray, listType);

เนื่องจากมีคนหลายคนในคอมเม้นท์ได้กล่าวถึงต่อไปนี้เป็นคำอธิบายถึงวิธีการใช้TypeTokenคลาส การก่อสร้างnew TypeToken<...>() {}.getType()รวบรวมประเภทเวลารวบรวม (ระหว่าง<และ>) ลงในjava.lang.reflect.Typeวัตถุรันไทม์ ซึ่งแตกต่างจากClassวัตถุซึ่งสามารถแสดงประเภท raw (ลบ) เท่านั้นTypeวัตถุสามารถเป็นตัวแทนประเภทใด ๆ ในภาษาจาวารวมถึงการสร้างอินสแตนซ์พารามิเตอร์ของประเภททั่วไป

TypeTokenระดับตัวเองไม่ได้มีการสร้างสาธารณะเพราะคุณไม่ควรที่จะสร้างได้โดยตรง แต่คุณจะสร้างคลาสย่อยที่ไม่ระบุตัวตนเสมอ ( {}ซึ่งเป็นส่วนที่จำเป็นของนิพจน์นี้)

เนื่องจากการลบประเภทTypeTokenคลาสสามารถจับประเภทที่เป็นที่รู้จักอย่างสมบูรณ์ในเวลารวบรวม (นั่นคือคุณไม่สามารถทำได้new TypeToken<List<T>>() {}.getType()สำหรับพารามิเตอร์ type T)

สำหรับข้อมูลเพิ่มเติมดูเอกสารประกอบสำหรับTypeTokenระดับ


31
ใน GSON เวอร์ชันใหม่ตัวสร้าง TypeToken นั้นไม่ใช่แบบสาธารณะดังนั้นที่นี่คุณจะได้รับข้อผิดพลาดที่ไม่สามารถมองเห็นได้ของ Constructor คุณต้องทำอะไรในกรณีนี้
Pablo

8
การใช้ GSON เวอร์ชันจริง (2.2.4) จะสามารถใช้งานได้อีกครั้ง คุณสามารถเข้าถึงตัวสร้างได้ที่นี่

วัตถุ json ของฉันเริ่มต้นด้วยวัตถุจากนั้นมีอาร์เรย์ของวัตถุที่ฉันต้องการ { "myObjectArray":[ {....} , {....} , {....} ] }ฉันได้สร้างไฟล์จำลองสำหรับ{....}ฉันจะได้รับรหัสชุดรวมทั่วไปนี้เพื่อไม่ถือว่าองค์ประกอบรากของฉันเป็นอาร์เรย์โดยไม่ต้องทำวัตถุไฟล์ซ้อนใหม่
CQM

7
จำเป็นต้องมีการนำเข้าต่อไปนี้ --- import java.lang.reflect.Type; นำเข้า com.google.gson.reflect.TypeToken
Umair Saleem

4
สิ่งนี้จะดีถ้า YourClass ได้รับการแก้ไขในรหัส เกิดอะไรขึ้นถ้าชั้นมาที่รันไทม์?
jasxir

273

อีกวิธีคือใช้อาร์เรย์เป็นชนิดเช่น:

MyClass[] mcArray = gson.fromJson(jsonString, MyClass[].class);

วิธีนี้จะช่วยให้คุณหลีกเลี่ยงความยุ่งยากทั้งหมดด้วยวัตถุประเภทและถ้าคุณต้องการรายการจริงๆคุณสามารถแปลงอาร์เรย์เป็นรายการโดย:

List<MyClass> mcList = Arrays.asList(mcArray);

IMHO นี้สามารถอ่านได้มากขึ้น

และเพื่อให้เป็นรายการจริง (ที่สามารถแก้ไขได้ดูข้อ จำกัด ของArrays.asList()) จากนั้นทำต่อไปนี้:

List<MyClass> mcList = new ArrayList<>(Arrays.asList(mcArray));

4
นี่มันเยี่ยมมาก! ฉันจะใช้มันอย่างไรกับการสะท้อน? ฉันไม่รู้MyClassคุณค่าและมันจะถูกกำหนดแบบไดนามิก!
Amin Sh

1
หมายเหตุ: ด้วยสิ่งนี้โปรดระวังว่า mcList ไม่ใช่รายการเต็มรูปแบบ หลายสิ่งจะไม่ทำงาน
njzk2

4
วิธีการใช้งานกับยาชื่อสามัญ? T[] yourClassList = gson.fromJson(message, T[].class);// ไม่สามารถเลือกจากตัวแปรประเภท
Pawel Cioch

2
@MateusViccari ในช่วงเวลาของความคิดเห็นนั้นในคำตอบนี้เป็นเพียงผลจากการเรียกไปmcList Arrays.asListเมธอดนี้ส่งคืนรายการซึ่งส่วนใหญ่ถ้าไม่ใช่เมธอดทางเลือกทั้งหมดจะไม่ถูกนำไปใช้งานและทิ้งข้อยกเว้น ตัวอย่างเช่นคุณไม่สามารถเพิ่มองค์ประกอบใด ๆ ลงในรายการนั้น ดังที่การแก้ไขในภายหลังแนะนำArrays.asListมีข้อ จำกัด และตัดเป็นจริงArrayListช่วยให้คุณสามารถรับรายการที่มีประโยชน์มากขึ้นในหลายกรณี
njzk2

2
หากคุณจำเป็นต้องสร้างประเภทอาร์เรย์ที่รันไทม์สำหรับประเภทองค์ประกอบโดยพลการที่คุณสามารถใช้Array.newInstance(clazz, 0).getClass()ตามที่อธิบายไว้ในคำตอบของดาวิดไม้
Daniel Pryden

31

อ้างถึงโพสต์นี้ Java Type Generic เป็นอาร์กิวเมนต์สำหรับ GSON

ฉันมีทางออกที่ดีกว่าสำหรับสิ่งนี้ นี่คือคลาส wrapper สำหรับลิสต์เพื่อให้ wrapper สามารถเก็บรายการชนิดที่แน่นอน

public class ListOfJson<T> implements ParameterizedType
{
  private Class<?> wrapped;

  public ListOfJson(Class<T> wrapper)
  {
    this.wrapped = wrapper;
  }

  @Override
  public Type[] getActualTypeArguments()
  {
      return new Type[] { wrapped };
  }

  @Override
  public Type getRawType()
  {
    return List.class;
  }

  @Override
  public Type getOwnerType()
  {
    return null;
  }
}

จากนั้นรหัสสามารถเป็นแบบง่าย:

public static <T> List<T> toList(String json, Class<T> typeClass)
{
    return sGson.fromJson(json, new ListOfJson<T>(typeClass));
}

คือmEntity.rulePatternอะไร
Al Lelopath

มันเป็นเพียงวัตถุตัวอย่างสำหรับการทดสอบ คุณไม่จำเป็นต้องสนใจมัน ใช้เมธอด toList และทุกอย่างเป็นไปด้วยดี
มีความสุขมากขึ้น

@Happier ฉันพยายามใช้ Gson 2.8.2 นี้และดูเหมือนว่าจะไม่ทำงาน ใด ๆ โอกาส stackoverflow.com/questions/50743932/...คุณสามารถดูและแจ้งให้เรารู้ว่าสิ่งที่ฉันหายไป
ปวีณ

1
@Praveen ฉันได้ลองด้วยวิธีนี้ใน 2.8.2 มันทำงานได้เหมือนเดิม
มีความสุขมากขึ้น

31

เนื่องจากGson 2.8เราสามารถสร้างฟังก์ชั่น util ได้เช่น

public <T> List<T> getList(String jsonArray, Class<T> clazz) {
    Type typeOfT = TypeToken.getParameterized(List.class, clazz).getType();
    return new Gson().fromJson(jsonArray, typeOfT);
}

ตัวอย่างการใช้

String jsonArray = ...
List<User> user = getList(jsonArray, User.class);

2
TypeToken#getParameterizedดูวิธีที่ดีกว่าจากนั้นแฮ็คที่มีคลาสย่อยที่ไม่ระบุชื่อ
Nikolay Kulachenko

นี่ควรเป็นคำตอบที่ยอมรับได้ อย่างน้อยสำหรับรุ่นที่ใหม่กว่า
ccpizza

2
นี่คือคำตอบที่สมบูรณ์แบบ แก้ปัญหาของฉัน
donmj

ฉันคัดลอกวิธีการของคุณ "ตามสภาพ" และไม่สามารถใช้งานได้: คอมไพเลอร์บอกว่า "วิธีการ getParameterized (Class <List>, Class <T>) ไม่ได้กำหนดไว้สำหรับประเภท TypeToken" ฉันตรวจสอบทั้งรุ่น Gson ของฉัน (2.8.0) และเอกสารประกอบและทุกอย่างเรียบร้อยในด้านนี้ ... ฉันลงเอยด้วยการใช้ @Happier solution ซึ่งใช้งานได้ดี
leguminator

@leguminator คุณนำเข้า TypeToken ถูกต้องหรือไม่ และคุณกำลังใช้ java หรือ kotlin ฉันจะลองทดสอบอีกครั้ง
ฟาน Van Linh

26

Wep อีกวิธีหนึ่งในการบรรลุผลลัพธ์เดียวกัน เราใช้มันเพื่อให้สามารถอ่านได้

แทนที่จะทำประโยคที่อ่านยากนี้:

Type listType = new TypeToken<ArrayList<YourClass>>(){}.getType();
List<YourClass> list = new Gson().fromJson(jsonArray, listType);

สร้างคลาสว่างที่ขยายรายการวัตถุของคุณ:

public class YourClassList extends ArrayList<YourClass> {}

และใช้งานเมื่อวิเคราะห์ JSON:

List<YourClass> list = new Gson().fromJson(jsonArray, YourClassList.class);

9

สำหรับ Kotlin เพียง:

import java.lang.reflect.Type
import com.google.gson.reflect.TypeToken
...
val type = object : TypeToken<List<T>>() {}.type

หรือนี่คือฟังก์ชันที่มีประโยชน์:

fun <T> typeOfList(): Type {
    return object : TypeToken<List<T>>() {}.type
}

จากนั้นใช้:

val type = typeOfList<YourMagicObject>()

ฉันใช้รหัสของคุณเพื่อสร้างฟังก์ชั่นนี้โดยใช้ประเภท reified: inline fun <reified T> buildType() = object : TypeToken<T>() {}.type!!และเรียกมันด้วยรายการประเภท:buildType<List<YourMagicObject>>()
coffeemakr

@coffeemakr คุณไม่จำเป็นต้องมีประเภท reified ที่นี่
Chad Bingham

โอ้ แต่ทำไมคุณถึงสร้างโทเค็นประเภทของ ArrayList ในbuildTypeและเรียกใช้ฟังก์ชันที่มีประเภททั่วไป? นี่คือการพิมพ์ผิดหรือไม่? - นี้จะสร้าง ArrayList <ArrayList <YourMagicObject >>
coffeemakr

@coffeemakr อ่าใช่ Typo
Chad Bingham

7
public static final <T> List<T> getList(final Class<T[]> clazz, final String json)
{
    final T[] jsonToObject = new Gson().fromJson(json, clazz);

    return Arrays.asList(jsonToObject);
}

ตัวอย่าง:

getList(MyClass[].class, "[{...}]");

สิ่งที่ดี. แต่DevNGคำตอบข้างต้นซ้ำซ้อนนี้เขียนขึ้นเมื่อ 2 ปีก่อน: stackoverflow.com/a/17300003/1339923 (และอ่านคำตอบสำหรับคำเตือนเกี่ยวกับแนวทางนี้)
Lambart

6

เมื่อตอบคำถามเดิมของฉันฉันยอมรับคำตอบของ doc_180 แล้ว แต่ถ้ามีใครเจอปัญหานี้อีกฉันจะตอบครึ่งหลังของคำถามด้วย:

NullPointerError ที่ฉันอธิบายไม่มีส่วนเกี่ยวข้องกับรายการ แต่มีเนื้อหา!

คลาส "MyClass" ไม่มีตัวสร้าง "no args" และไม่มีคลาสซูเปอร์คลาส เมื่อฉันเพิ่ม Constructor "MyClass ()" ที่เรียบง่ายให้กับ MyClass และ superclass ของมันทุกอย่างทำงานได้ดีรวมถึง List serialization และ deserialization ตามที่ doc_180 แนะนำ


1
หากคุณมีรายการคลาสนามธรรมคุณจะได้รับข้อผิดพลาดเดียวกัน ฉันเดาว่านี่เป็นข้อผิดพลาดทั่วไปของ GSON สำหรับ "ไม่สามารถยกตัวอย่างชั้นเรียน"
ดึง

เคล็ดลับเกี่ยวกับการเพิ่มนวกรรมิกช่วยให้ฉันรู้ว่าทำไมฉันจึงมีค่า Null ทั้งหมด ฉันมีชื่อฟิลด์เช่น "ถึง" และ "จาก" ในสตริง JSON ของฉัน แต่ฟิลด์ที่เกี่ยวข้องในวัตถุของฉันคือ "ถึง" และ "จาก" ในกรณีที่ต่ำกว่าดังนั้นพวกเขาจึงถูกข้าม
Rune

4

นี่คือโซลูชันที่ทำงานกับชนิดที่กำหนดแบบไดนามิก เคล็ดลับคือการสร้างประเภทของอาร์เรย์ที่เหมาะสมโดยใช้ Array.newInstance ()

    public static <T> List<T> fromJsonList(String json, Class<T> clazz) {
    Object [] array = (Object[])java.lang.reflect.Array.newInstance(clazz, 0);
    array = gson.fromJson(json, array.getClass());
    List<T> list = new ArrayList<T>();
    for (int i=0 ; i<array.length ; i++)
        list.add(clazz.cast(array[i]));
    return list; 
}

คำตอบนี้จะดีกว่าถ้าคุณใช้เพื่อหลีกเลี่ยงการถูกตรวจสอบคำเตือนที่เกิดจากการส่งไปยังclass.cast() (T)หรือดีกว่ายังไม่ต้องรำคาญกับการหล่อและการใช้งานบางอย่างเช่นการแปลงจากอาร์เรย์หนึ่งArrays.asList() List<T>นอกจากนี้ยังไม่จำเป็นต้องผ่านความยาวให้Array.newInstance()- อาร์เรย์ของขนาดเป็นศูนย์จะเป็นที่ดีพอที่จะเรียกgetClass()ใน
Daniel Pryden

ขอขอบคุณสำหรับข้อเสนอแนะฉันได้ทำการเปลี่ยนแปลงที่แนะนำและอัปเดตโพสต์ด้านบน
David Wood

2

ฉันต้องการเพิ่มความเป็นไปได้อีกหนึ่งอย่าง หากคุณไม่ต้องการใช้ TypeToken และต้องการแปลงอาร์เรย์วัตถุ json เป็น ArrayList คุณสามารถดำเนินการดังนี้:

หากโครงสร้าง json ของคุณเป็นเช่น:

{

"results": [
    {
        "a": 100,
        "b": "value1",
        "c": true
    },
    {
        "a": 200,
        "b": "value2",
        "c": false
    },
    {
        "a": 300,
        "b": "value3",
        "c": true
    }
]

}

และโครงสร้างชั้นเรียนของคุณเป็นเช่น:

public class ClassName implements Parcelable {

    public ArrayList<InnerClassName> results = new ArrayList<InnerClassName>();
    public static class InnerClassName {
        int a;
        String b;
        boolean c;      
    }
}

จากนั้นคุณสามารถแยกวิเคราะห์ได้เช่น:

Gson gson = new Gson();
final ClassName className = gson.fromJson(data, ClassName.class);
int currentTotal = className.results.size();

ตอนนี้คุณสามารถเข้าถึงแต่ละองค์ประกอบของวัตถุ className


1

อ้างถึงตัวอย่างที่ 2 สำหรับความเข้าใจระดับ 'ประเภท' ของ Gson

ตัวอย่างที่ 1: ใน deserilizeResturant นี้เราใช้ Employee [] array และรับรายละเอียด

public static void deserializeResturant(){

       String empList ="[{\"name\":\"Ram\",\"empId\":1},{\"name\":\"Surya\",\"empId\":2},{\"name\":\"Prasants\",\"empId\":3}]";
       Gson gson = new Gson();
       Employee[] emp = gson.fromJson(empList, Employee[].class);
       int numberOfElementInJson = emp.length();
       System.out.println("Total JSON Elements" + numberOfElementInJson);
       for(Employee e: emp){
           System.out.println(e.getName());
           System.out.println(e.getEmpId());
       }
   }

ตัวอย่างที่ 2:

//Above deserilizeResturant used Employee[] array but what if we need to use List<Employee>
public static void deserializeResturantUsingList(){

    String empList ="[{\"name\":\"Ram\",\"empId\":1},{\"name\":\"Surya\",\"empId\":2},{\"name\":\"Prasants\",\"empId\":3}]";
    Gson gson = new Gson();

    // Additionally we need to se the Type then only it accepts List<Employee> which we sent here empTypeList
    Type empTypeList = new TypeToken<ArrayList<Employee>>(){}.getType();


    List<Employee> emp = gson.fromJson(empList, empTypeList);
    int numberOfElementInJson = emp.size();
    System.out.println("Total JSON Elements" + numberOfElementInJson);
    for(Employee e: emp){
        System.out.println(e.getName());
        System.out.println(e.getEmpId());
    }
}

0

ฉันชอบคำตอบจาก kays1 แต่ไม่สามารถใช้งานได้ ดังนั้นฉันจึงสร้างเวอร์ชั่นของตัวเองโดยใช้แนวคิดของเขา

public class JsonListHelper{
    public static final <T> List<T> getList(String json) throws Exception {
        Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create();
        Type typeOfList = new TypeToken<List<T>>(){}.getType();
        return gson.fromJson(json, typeOfList);
    }
}

การใช้งาน:

List<MyClass> MyList= JsonListHelper.getList(jsonArrayString);

แน่นอนว่ามันไม่สามารถใช้งานได้เนื่องจากคุณพยายามใช้ T ในการรวบรวมเวลา สิ่งนี้จะยกเลิกการทำให้รายชื่อ StringMap มีประสิทธิภาพหรือไม่?
JHH

0

ในคำตอบของ case @ uncaught_exceptions ของฉันไม่ทำงานฉันต้องใช้List.classแทนjava.lang.reflect.Type:

String jsonDuplicatedItems = request.getSession().getAttribute("jsonDuplicatedItems").toString();
List<Map.Entry<Product, Integer>> entries = gson.fromJson(jsonDuplicatedItems, List.class);
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.