web-dev-qa-db-ja.com

com.google.gson.internal.LinkedTreeMapをクラスにキャストできません

JSON文字列からオブジェクトを取得する際に問題があります。

クラスを取得しましたProduct

public class Product {
    private String mBarcode;
    private String mName;
    private String mPrice;

    public Product(String barcode, String name, String price) {
        mBarcode = barcode;
        mName = name;
        mPrice = price;
    }

    public int getBarcode() {
        return Integer.parseInt(mBarcode);
    }

    public String getName() {
        return mName;
    }

    public double getPrice() {
        return Double.parseDouble(mPrice);
    }
}    

サーバーからJSON文字列表現でArrayList<Product>を取得します。例えば:

[{"mBarcode":"123","mName":"Apfel","mPrice":"2.7"},
{"mBarcode":"456","mName":"Pfirsich","mPrice":"1.1111"},
{"mBarcode":"89325982","mName":"Birne","mPrice":"1.5555"}] 

この文字列は次のように生成されます。

public static <T> String arrayToString(ArrayList<T> list) {
    Gson g = new Gson();
    return g.toJson(list);
}

オブジェクトを取り戻すには、この関数を使用します。

public static <T> ArrayList<T> stringToArray(String s) {
    Gson g = new Gson();
    Type listType = new TypeToken<ArrayList<T>>(){}.getType();
    ArrayList<T> list = g.fromJson(s, listType);
    return list;
}

しかし、呼び出すとき

String name = Util.stringToArray(message).get(i).getName();

エラーが発生しましたcom.google.gson.internal.LinkedTreeMapをobject.Productにキャストできません

私は何を間違えていますか? LinkedTreeMapsのリストを作成したように見えますが、それらをProductオブジェクトに変換するにはどうすればよいですか?

53
Tanzmaus

私の意見では、型の消去により、パーサーは実行時に実際の型Tをフェッチできません。回避策の1つは、メソッドへのパラメーターとしてクラス型を提供することです。

このようなものは、確かに他の可能な回避策がありますが、私はこれが非常に明確で簡潔であると思います。

public static <T> List<T> stringToArray(String s, Class<T[]> clazz) {
    T[] arr = new Gson().fromJson(s, clazz);
    return Arrays.asList(arr); //or return Arrays.asList(new Gson().fromJson(s, clazz)); for a one-liner
}

そして、それを次のように呼び出します:

String name = stringToArray(message, Product[].class).get(0).getName();
84
Alexis C.

また、LinkedTreeMapsのキャストについて不満を言うGSONの問題もありました。

Alexisが提供する answer およびAljoschaが提供する comment は、エラーが発生する理由を説明しています。 「タイプのジェネリックは通常、実行時に消去されます。」私の問題は、通常実行したときにコードが機能することでしたが、ProGuardを使用すると、キャストに不可欠なコードが削除されました。

Alexisの答えに従うと、キャストをより明確に定義でき、問題を解決できます。 Googleから提供されたProGuard rules を追加することもできます(これを行うだけで問題が解決しました)。

##---------------Begin: proguard configuration for Gson  ----------
# Gson uses generic type information stored in a class file when working with fields. Proguard
# removes such information by default, so configure it to keep all of it.
-keepattributes Signature

# For using GSON @Expose annotation
-keepattributes *Annotation*

# Gson specific classes
-keep class Sun.misc.Unsafe { *; }
#-keep class com.google.gson.stream.** { *; }

# Application classes that will be serialized/deserialized over Gson
-keep class com.google.gson.examples.Android.model.** { *; }

##---------------End: proguard configuration for Gson  ----------

ストーリーの教訓:必要なProGuardルールを常に確認してください。

15
welshk91

Alexis Cの回答に似ています。しかし、コトリンで。
クラス型を関数に渡すだけで、ジェネリック型とは何かを明確にします。
これは簡単な例です。

inline fun <reified T> parseArray(json: String, typeToken: Type): T {
    val gson = GsonBuilder().create()
    return gson.fromJson<T>(json, typeToken)
}

通話例

fun test() {
    val json: String = "......."
    val type = object : TypeToken<List<MyObject>>() {}.type
    val result: List<MyObject> = parseArray<List<MyObject>>(json = json, typeToken = type)
    println(result)
}
6
Johnny

同じ問題がありました。引数としてリストがある場合にのみ発生することに気付きました。

私の解決策は、リストを別のオブジェクトでラップすることです。

class YourObjectList {

    private List<YourObject> items;

    // constructor, getter and setter
}

その単一のオブジェクトでは、クラスキャストの例外に関する問題はもうありませんでした。

1
kaiser

また、署名済みビルドのみのcom.google.gson.internal.LinkedTreeMapのクラスキャスト例外に直面しました。これらの行をprogurardに追加しました。その後、正常に動作します。

-keepattributes署名

-keepattributes 注釈

-keep class com.google。** {*; }

-クラスを保持Sun.misc。** {*; }

1
Ganesan

JSON

{
    results: [
    {
        id: "10",
        phone: "+91783XXXX345",
        name: "Mr Example",
        email: "[email protected]"
    },
    {
        id: "11",
        phone: "+9178XXXX66",
        name: "Mr Foo",
        email: "[email protected]"
    }],
    statusCode: "1",
    count: "2"
}

listView BaseAdapter fileでは、LinkedTreeMap Key Valueオブジェクトを使用してデータをマップし、次のように行属性値を取得する必要があります。

...
...

    @Override
    public View getView(final int i, View view, ViewGroup viewGroup) {
        if(view==null)
        {
            view= LayoutInflater.from(c).inflate(R.layout.listview_manage_clients,viewGroup,false);
        }

        TextView mUserName = (TextView) view.findViewById(R.id.userName);
        TextView mUserPhone = (TextView) view.findViewById(R.id.userPhone);


        Object getrow = this.users.get(i);
        LinkedTreeMap<Object,Object> t = (LinkedTreeMap) getrow;
        String name = t.get("name").toString();

        mUserName.setText("Name is "+name);
        mUserPhone.setText("Phone is "+phone);

        return view;
    }
...
...

Androidの例でRetrofit2を使用したJSONデータからのListView

ソースリンク

1
Code Spy

解析時にgsonで独自のArrayList<MyObject>を使用する場合;

Type typeMyType = new TypeToken<ArrayList<MyObject>>(){}.getType();

ArrayList<MyObject> myObject = gson.fromJson(jsonString, typeMyType)
1
murat gursel
{"root": 
 [
  {"mBarcode":"123","mName":"Apfel","mPrice":"2.7"},
  {"mBarcode":"456","mName":"Pfirsich","mPrice":"1.1111"},
  {"mBarcode":"89325982","mName":"Birne","mPrice":"1.5555"}
 ]
} 


JsonObject root = g.fromJson(json, JsonObject.class);
//read root element which contains list
JsonElement e = root.get("root");
//as the element is array convert it 
JsonArray ja  = e.getAsJsonArray();

for(JsonElement j : ja){
   //here use the json to parse into your custom object 
}
0
Ozzie

ここで既に述べた答えに追加するために、たとえばHTTP呼び出しを処理するジェネリッククラスがある場合、コンストラクタの一部としてClass<T>を渡すと便利かもしれません。

少し詳しく説明すると、Javaは、実行時にTだけではClass<T>を推測できないため、これが発生します。決定を行うには、実際のソリッドクラスが必要です。

だから、あなたがこのようなものを持っているなら、私がそうするように:

class HttpEndpoint<T> implements IEndpoint<T>

継承コードがclass<T>も送信できるようにすることができます。これは、その時点でTが何であるかが明らかだからです。

public HttpEndpoint(String baseurl, String route, Class<T> cls) {
    this.baseurl = baseurl;
    this.route = route;
    this.cls = cls;
}

継承クラス:

public class Players extends HttpEndpoint<Player> {

    public Players() {
        super("http://127.0.0.1:8080", "/players",  Player.class);
    }
}

完全にクリーンなソリューションではありませんが、コードをパッケージ化し、メソッド間でClass<T>する必要はありません。

0
Serguei Fedorov

解析時にこれを使用します

  public static <T> List<T> parseGsonArray(String json, Class<T[]> model) {
    return Arrays.asList(new Gson().fromJson(json, model));
}
0
MFQ