94

I have some problems with getting my object from a JSON string.

I got the class 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);
    }
}    

From my server I get an ArrayList<Product> in JSON String representation. For example:

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

This String is generated like this:

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

To get my Object back I use this function:

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;
}

But when calling

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

I get the error com.google.gson.internal.LinkedTreeMap cannot be cast to object.Product

What am I doing wrong? It looks like it created a List of LinkedTreeMaps but how do i convert those into my Product Object?

friederbluemle
  • 33,549
  • 14
  • 108
  • 109
Tanzmaus
  • 1,071
  • 1
  • 7
  • 7
  • 1
    Try this https://www.freakyjolly.com/com-google-gson-internal-linkedtreemap-cannot-be-cast-to-how-to-resolve/ – Code Spy Aug 27 '18 at 06:49
  • Try this -keepattributes Signature – Vivek Gupta Nov 26 '19 at 14:06
  • You can iterate through the `ArrayList` to convert each `LinkedTreeMap` item to a `Product` item. You can use `Gson` a second time to do the conversion. See example code here: https://randomgyan.com/mapping-linkedtreemap-response-to-class-using-gson-utility/ – Mr-IDE May 31 '21 at 19:30

10 Answers10

112

In my opinion, due to type erasure, the parser can't fetch the real type T at runtime. One workaround would be to provide the class type as parameter to the method.

Something like this works, there are certainly other possible workarounds but I find this one very clear and concise.

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
}

And call it like:

String name = stringToArray(message, Product[].class).get(0).getName();
Alexis C.
  • 91,686
  • 21
  • 171
  • 177
  • 5
    This answer provides a workaround, but does not address the problem of nested generics. See these two answers for a more general solution: http://stackoverflow.com/a/14506181/4745878 http://stackoverflow.com/a/26203635/4745878 – Aljoscha Meyer Apr 01 '16 at 21:26
  • @AljoschaMeyer +1 for the useful links. – VictorB Nov 09 '18 at 08:02
  • If you are trying to use GSON + Room, that solution will not work, because Room doesn't allow generic field type. This works https://stackoverflow.com/a/52224425/6009482 – Valgaal Jan 18 '19 at 09:06
27

Similar to Alexis C's answers. but in Kotlin.
Just pass the class type into function and clarify what generic type is.
Here is simplified example.

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

Here is example call

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)
}
Johnny
  • 1,824
  • 23
  • 16
21

I also had problems with GSON complaining about casting LinkedTreeMaps.

The answer provided by Alexis and the comment by Aljoscha explains why the error occurs; "Generics on a type are typically erased at runtime." My issue was that my code worked when I ran it normally, but using ProGuard caused code to be stripped that was vital to casting.

You can follow Alexis's answer and more clearly define the cast and that should fix the problems. You can also add the ProGuard rules given by Google (simply doing this cleared the issue up for me).

##---------------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  ----------

Moral of the Story: Always check to see what ProGuard rules you need.

Community
  • 1
  • 1
welshk91
  • 1,613
  • 18
  • 16
  • I had to define my custom base response abstract class to keep. `-keep class * extends in.krsh.app.webservice.BaseResponseBody`. The class is as like `internal abstract class BaseResponseBody { }`. – krishh Oct 06 '17 at 19:01
12

If you use your own ArrayList<MyObject> in gson when parsing;

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

ArrayList<MyObject> myObject = gson.fromJson(jsonString, typeMyType)
Valgaal
  • 896
  • 10
  • 25
muratGursel
  • 131
  • 1
  • 5
3

For JSON

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

In listView BaseAdapter file we need to map data using LinkedTreeMap Key Value object to get row attribute value as below:

...
...

    @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;
    }
...
...

ListView from JSON Data using Retrofit2 in Android Example

Source Link

Code Spy
  • 9,626
  • 4
  • 66
  • 46
  • Thanks ,solved my problem.My Kotlin code: val t: LinkedTreeMap<*, *> = scoreValueEntity as LinkedTreeMap<*, *> t["value"]?.toString()?.toFloat() ?: 0f – Rickon Gao Jul 29 '22 at 05:56
2

I also faced class cast exception of com.google.gson.internal.LinkedTreeMap for my signed build only. I added these below lines in progurard. Then it works fine.

-keepattributes Signature

-keepattributes *Annotation*

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

-keep class sun.misc.** { *; }
OneWorld
  • 17,512
  • 21
  • 86
  • 136
Ganesan
  • 59
  • 5
  • 3
    Please add more information and explain the solution. – J Fabian Meier Jul 28 '16 at 14:16
  • You can add this in your proguard and will work fine with signed build – Ganesan Jul 28 '16 at 16:05
  • 7
    Those are extremely broad Proguard rules. Rules that keep huge swathes of code defeat the purpose of Proguard, since it cannot strip out the unused code in those packages which is the entire reason you are using Proguard. The keep statements should go all the way down to just the specific class you need, rather than keeping all of com.google.** and sun.misc.**. The correct rule is something like "-keep class com.google.api.services.vision.v1.model.BatchAnnotateImagesResponse { ; }", depending where the GSON model class you are using is stored. – OldSchool4664 Nov 01 '16 at 00:06
2

I had the same problem. I noticed it only occures when you have List as argument.

My solution is to wrap the list in another Object:

class YourObjectList {

    private List<YourObject> items;

    // constructor, getter and setter
}

With that single object, i had no more problems with class cast exception.

kaiser
  • 940
  • 1
  • 10
  • 25
  • This is the easiest and most correct solution. Serializing and Deserializing a naked list of objects causes problems but if you wrap a plain java bean around the list, everything works perfectly. – Jim W Apr 23 '22 at 05:21
1

use this when parsing

  public static <T> List<T> parseGsonArray(String json, Class<T[]> model) {
    return Arrays.asList(new Gson().fromJson(json, model));
}
MFQ
  • 839
  • 8
  • 14
0
{"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 
}
Ozzie
  • 29
  • 3
0

To add to the answers already mentioned here, if you have a generic class that handles, say, HTTP calls, it maybe useful to pass Class<T> as part of the constructor.

To give a little more detail, this happens because Java cannot infer the Class<T> during runtime with just T. It needs the actual solid class to make the determination.

So, if you have something like this, like I do:

class HttpEndpoint<T> implements IEndpoint<T>

you can allow the inheriting code to also send the class<T>, since at that point is it clear what T is.

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

inheriting class:

public class Players extends HttpEndpoint<Player> {

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

while not entirely a clean solution, it does keep the code packaged up and you don't have to Class<T> between methods.

Serguei Fedorov
  • 7,763
  • 9
  • 63
  • 94