2

I am trying to make a generic method for data deserialization.

My code:

public <T> ExportedData<T> getExportData(T classType, String exportUri) {
    Response response = _client.get(exportUri);
    // System.out.println(response.body.toString());
    ExportedData<T> exportedData = GsonSingleton.getGson().fromJson(response.body.toString(), new TypeToken<ExportedData<T>>() {
        }.getType());
    return exportedData;
}

The response.body:

{"totalResults":2,"limit":50000,"offset":0,"count":2,"hasMore":false,"items":[{"DevicesIDs":"","EmailAddress":"zatokar@gmail.com"},{"DevicesIDs":"","EmailAddress":"oto@increase.dk"}]}

The way I call the generic method:

ExportedData<AccengageOutboundContact> exportedData = generalBulkHelper.getExportData(new AccengageOutboundContact(), uriLimitAndOffset);

The AccengageOutboundContact:

public class AccengageOutboundContact {

   public String EmailAddress;
   public String DevicesIDs;

}

And the ExportedData:

public class ExportedData<T> {
    public int totalResults;
    public int limit;
    public int offset;
    public int count;
    public boolean hasMore;
    public List<T> items;
}

I would expect to get an ArrayList of AccengageOutboundContact objects. What I am getting is ArrayList of StringMap.

Any idea what am I doing wrong?

Ondrej Tokar
  • 4,898
  • 8
  • 53
  • 103

1 Answers1

2

I've seen this one a bunch of times, but there does not seem to be a good duplicate to link.


Basically the problem is that T is erased to Object, in your generic method. So the TypeToken that is created, does not hold the needed information.

This results in deserializtion to a StringMap.


You can fix this by passing a complete TypeToken to your method:

public <T> ExportedData<T> getExportData(TypeToken<ExportedData<T>> tt, String exportUri) {
    Response response = _client.get(exportUri);
    // System.out.println(response.body.toString());
    ExportedData<T> exportedData = GsonSingleton.getGson().fromJson(response.body.toString(),
        tt.getType());
    return exportedData;
}

Then call like:

generalBulkHelper.getExportData(new TypeToken<ExportedData<AccengageOutboundContact>>(){},
    uriLimitAndOffset);
Jorn Vernee
  • 31,735
  • 4
  • 76
  • 93
  • Thank you, will try it, but why is T erased to Object? – Ondrej Tokar Aug 30 '16 at 14:19
  • 1
    Because [that's how Java's generics works](http://stackoverflow.com/questions/339699/java-generics-type-erasure-when-and-what-happens). – Kayaman Aug 30 '16 at 14:20
  • So are you saying that T is always erased to an Object? If so, to me the generic methods are losing point, we could always just use objects and just cast it to what we want, right? – Ondrej Tokar Aug 30 '16 at 14:22
  • @OndrejTokar Yes, but you would have to cast, and the compiler can not see if that cast is correct at compile time. – Jorn Vernee Aug 30 '16 at 14:23
  • Hmm I see (almost). Thank you – Ondrej Tokar Aug 30 '16 at 14:23
  • @JornVernee would you mind telling me why doesn't T get erased e.g. in collection? Or if it does, how is it handled? – Ondrej Tokar Aug 30 '16 at 14:29
  • 2
    @OndrejTokar It does get erased, all generics do, it was done to be backward compatible with VMs that were made before the addition of generics. You could also check out [Why Use Generics?](https://docs.oracle.com/javase/tutorial/java/generics/why.html), it shows some "before and after" code. The compiler will insert casts back from `Object` to the required type (after some thorough type checking, which could be hard to do by hand). – Jorn Vernee Aug 30 '16 at 14:38
  • Thank you. Helped a lot! – Ondrej Tokar Aug 30 '16 at 14:40