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オブジェクトに変換するにはどうすればよいですか?
私の意見では、型の消去により、パーサーは実行時に実際の型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();
また、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ルールを常に確認してください。
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)
}
同じ問題がありました。引数としてリストがある場合にのみ発生することに気付きました。
私の解決策は、リストを別のオブジェクトでラップすることです。
class YourObjectList {
private List<YourObject> items;
// constructor, getter and setter
}
その単一のオブジェクトでは、クラスキャストの例外に関する問題はもうありませんでした。
また、署名済みビルドのみのcom.google.gson.internal.LinkedTreeMapのクラスキャスト例外に直面しました。これらの行をprogurardに追加しました。その後、正常に動作します。
-keepattributes署名
-keepattributes 注釈
-keep class com.google。** {*; }
-クラスを保持Sun.misc。** {*; }
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;
}
...
...
解析時にgsonで独自のArrayList<MyObject>
を使用する場合;
Type typeMyType = new TypeToken<ArrayList<MyObject>>(){}.getType();
ArrayList<MyObject> myObject = gson.fromJson(jsonString, typeMyType)
{"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
}
ここで既に述べた答えに追加するために、たとえば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>
する必要はありません。
解析時にこれを使用します
public static <T> List<T> parseGsonArray(String json, Class<T[]> model) {
return Arrays.asList(new Gson().fromJson(json, model));
}