Weird behaviour of GSON - java

I'm using Google's JSON library called Gson in one of my project.
I have a code for converting JSON String into object using GSON. I have following method to do that:
public static <T> ApiResponse<T> fromJson(String json)
{
return new Gson().fromJson(json, new TypeToken<ApiResponse<T>>() {}.getType());
}
And it seems to work fine when I do something like that:
ApiResponse<List<JobModel>> response = ApiResponse.fromJson(new String(bytes));
OR
ApiResponse<Double> response = ApiResponse.fromJson(new String(bytes));
But when I try do this:
ApiResponse<JobModel> response = ApiResponse.fromJson(new String(bytes));
Where JobModel is my own class I get the following error:
com.google.gson.internal.LinkedTreeMap cannot be cast to com.pcf.api.model.JobModel
So then I went and implemented another method in ApiResponse:
public static <T> ApiResponse<T> fromJson(String json, TypeToken<ApiResponse<T>> token)
{
return new Gson().fromJson(json, token.getType());
}
And this time call it using function above:
ApiResponse<JobModel> response = ApiResponse.fromJson(new String(bytes), new TypeToken<ApiResponse<JobModel>>() {});
It seems to work fine.
I just can't get my head around this as two functions do exactly same thing. The only difference is that in first it purely relies on Java's generics where in second one I pass TypeToken as a parameter.
Can anyone explain me why is that happening and is there any way to fix it ?

A TypeToken is kind of a hack with generics. It depends on subclassing the type, either with an anonymous or normal class, and using Class#getGenericSuperclass() which states
If the superclass is a parameterized type, the Type object returned
must accurately reflect the actual type parameters used in the source
code.
In other words, in an anonymous class declaration like this
new TypeToken<ApiResponse<T>>() {}.getType())
the superclass is TypeToken<ApiResponse<T>>. It's equivalent to
class Subclass extends TypeToken<ApiResponse<T>>
assuming T was in scope. So when you call Class#getGenericSuperclass(), it will return a ParameterizedType that knows about ApiReponse<T> since that is the actual type parameters used in the source code.
When you call your original function with any of
ApiResponse<List<JobModel>> response = ApiResponse.fromJson(new String(bytes));
ApiResponse<Double> response = ApiResponse.fromJson(new String(bytes));
ApiResponse<JobModel> response = ApiResponse.fromJson(new String(bytes));
although the compiler will infer and bind the corresponding type as a type argument to the method invocation, the internals of the method will pass the same TypeToken object with ApiResponse<T>. Since Gson doesn't know what T is, it will use a default that depends on what it sees in the JSON. If it sees an object, it will use a LinkedTreeMap. If it sees a numeric primitive, it will use the double. Etc.
In the case where you pass a TypeToken,
ApiResponse.fromJson(new String(bytes), new TypeToken<ApiResponse<JobModel>>() {});
it's equivalent to
class Subclass extends TypeToken<ApiResponse<JobModel>>
In other words, Class#getGenericSuperclass() will return a ParameterizedType that has ApiResponse<JobModel>. Gson can extract the JobModel and use it as a hint for deserializing the JSON.
Can anyone explain me why is that happening and is there any way to
fix it ?
There's nothing really to fix. That's just how it works.
Additional reading:
is it possible to use Gson.fromJson() to get ArrayList<ArrayList<String>>?
Gson TypeToken with dynamic ArrayList item type
how does the method infer the type of <T>

Generics work at compile-time,due to lack of reified Generics in Java (it isn't possible to do a T t = new T()), Gson itself is forced to use the TypeToken approach, as you see. Otherwise Gson would have done it in a much more elegant manner.

Related

How to determine class of object from GSON parse?

I am parsing JSON string from a byte-array and casting it as an object.
How do I determine the class of the object?
Object objDeserialized = gson.fromJson(jsonFromString, Object.class);
//It could be type Message or RoomDetail
gson.fromJson(jsonFromString, Object.class);
In general, this won't work because of Object.class. Gson prohibits overriding the Object class deserialization and uses ObjectTypeAdapter (see the primary Gson constructor as of Gson 2.8.0 and probably much earlier):
// built-in type adapters that cannot be overridden
factories.add(TypeAdapters.JSON_ELEMENT_FACTORY);
factories.add(ObjectTypeAdapter.FACTORY);
// the excluder must precede all adapters that handle user-defined types
factories.add(excluder);
// user's type adapters
factories.addAll(typeAdapterFactories);
If you want to use Object.class, you have to cast the result to either a primitive wrapper, null, or a List<E> or Map<K,V> -- and make some sort of analysis yourself. The rationale behind it is that you must know the result class in advance to make sure you're getting a proper deserialized object.
The best thing you can do here is making your custom parent super-type (does not really matter if it's a class or an interface), say class Message extends Base and class RoomDetail extends Base, and then registering a JsonDeserializer<Base> implementation to a GsonBuilder which can attempt to detect the real type of the Base instance. After that you can do:
gson.fromJson(jsonSource, Base.class);
See more:
Polymorphic objects deserialization:
How to parse dynamic json in android with retrofit 2 using annotations
How do I parse a nested JSON array of object with a custom Gson deserializer?
Json response parser for Array or Object
Google Gson extras, never been published as artifacts, but may be an inspiration point for you:
https://github.com/google/gson/blob/master/extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java
If you do not know the type of the JSON you want to parse you could use the JsonParser from the Gson lib to parse the JSON instead of the Gson class directly. e.g.
JsonParser parser = new JsonParser(jsonFromString);
JsonObject obj = parser.parse().getAsJsonObject();
You could then look at the properties of the JsonObject you have created to see what it is. e.g.
if (obj.has("somePropertyNameIKnownIsAMemberOfRoomDetail")) {
RoomDetail roomDetail = gson.fromJson(jsonFromString, RoomDetail.class);
} else {
Message message = gson.fromJson(jsonFromString, Message.class);
}

How can I determine the unerased type of a field?

I have a generic response wrapper class:
public class Response <T> {
T response;
}
and unrelated classes to be wrapped:
public class ServiceResponse {
String someField;
}
When I make a service request, I get a JSON response that looks something like:
{ "code":200, "response":{"someField":"some text"} }
Now, all my service responses have the same outer wrapper, i.e., they all have:
{ "code":200, "timestamp":"....", "response":... }
But the actual format/type of the response field is different for each service request. When I deserialize the response, I need to know the type of the response field so I can create the appropriate instance, if the deserialization was done within Response, I could use:
response = new T(jsonParser);
However, I'm doing all of this from within a library that is driven by reflection, so I normally deserialize the whole tree with code like:
wrapper = deserializer.parseObject(Response<ServiceResponse>.class)
but, at this point my parseObject method can't correctly determine the type of T.
I can use something like:
Response<ServiceResponse> response = new Response<>();
Field field = response.getClass().getDeclaredField("response");
Type type = field.getGenericType();
which then tells me that response is of type T but what I actually need is ServiceResponse
Per this SO question I tried casting as ParameterizedType but that would actually seem to apply to a field of type Response<ServiceResponse> and not the actual field within (and it fails because type can't be cast as ParameterizedType)
Is there any way to determine (at run time) the raw type of response?
Eventually, I may wind up having to create an annotation providing more details about how to deserialize the field, probably by providing a function to do it, but would prefer a more transparent approach.
Another possibility might be to actually assign a void instance of T to response at initialization time and then I could grab the actual type from that...
Check out this post:
http://mydailyjava.blogspot.com/2013/06/advanced-java-generics-retreiving.html
It's actually exactly what you're looking for.
According to this, you'll just need to extend your Response class and then query the generic type of its super.

how to get a class reference to parameterized type

Is there any chance to assign to class reference the parameterized type eg.
Class<Set> c1= Set.class; //OK
Class<Set<Integer>> c2 = Set<Integer>.class; //Makes error
Using .class literal with a class name, or invoking getClass() method on an object returns the Class instance, and for any class there is one and only one Class instance associated with it.
Same holds true for a generic type. A class List<T> has only a single class instance, which is List.class. There won't be different class types for different type parameters. This is analogous to how C++ implements generics, where each generic type instantiation will have a separate Class instance. So in Java, you can't do Set<Integer>.class. Java doesn't allow that because it doesn't make sense, and might give wrong intentions about number of Class instances.
However, if you want a Class<Set<Integer>>, you can achieve that will a bit of type casting (which will be safe), as shown below:
Class<Set<Integer>> clazz = (Class<Set<Integer>>)(Class<?>) Set.class;
This will work perfectly fine.
You can't do it this way, because the type-parameter information is gone at Runtime and the .class statement is actually evaluated then.
You can only do:
Set<Integer> someSet = ..
Class<?> c2 = someSet.class;
new ParameterizedTypeReference<List<ClassName>>() {} Should work.
Example is giving below for exchange method Rest Template class exchange function
Funcation Requirement
public <T> ResponseEntity<T> exchange(URI url, HttpMethod method, #Nullable HttpEntity<?> requestEntity, ParameterizedTypeReference<T> responseType) throws RestClientException
Implementation
restTemplate.exchange(new URI("Http://{host}:{port}/{url}"), HttpMethod.POST, customObject, new ParameterizedTypeReference<List<ResponseClassName>>() {});

Unable to get a generic ResponseEntity<T> where T is a generic class "SomeClass<SomeGenericType>"

Please help me to get a ResponseEntity<T> where T is itself a generic type. As I see it of now, this is not supported nowdays by spring RestTemplate. I'm using Spring MVC version 3.1.2
Here is my code, that I want to use:
Code:
ResponseEntity<CisResponse<CisResponseEntity>> res =
this.restTemplate.postForEntity(
this.rootURL, myRequestObj, CisResponse.class);
I'm getting this error:
Type mismatch: cannot convert from ResponseEntity<CisResponse> to
ResponseEntity<CisResponse<CisResponseEntity>>
It's obvious error, but how I can workaround it today?
Than I do want to get my generic response type:
CisResponse<CisResponseEntity> myResponse= res.getBody();
CisResponseEntity entity = myResponse.getEntityFromResponse();
For now, I use this solution, with postForObject() and not postForEntity():
CisResponse<CisResponseEntity> response =
this.restTemplate.postForObject(
this.rootURL,myRequestObj, CisResponse.class);
This was a known issue. Now it's fixed with the introduction of ParameterizedTypeReference, which is a parameterized type that you explicitely inherit to supply type information at runtime. This is called a super-type token, and works around type erasure because subclasses (anoniymous in this case) keep the type arguments of the generic supertype at runtime.
However you can't use postForObject, because the API only supports exchange():
ResponseEntity<CisResponse<CisResponseEntity>> res = template.exchange(
rootUrl,
HttpMethod.POST,
null,
new ParameterizedTypeReference<CisResponse<CisResponseEntity>>() {});
Note that the last line demonstrates the idea of super type tokens: you don't supply the literal CisResponse.class, but an anonymous instantiation of the parameterized type ParameterizedTypeReference<T>, which at runtime can be expected to extract subtype information. You can think of super type tokens as hacks for achieving Foo<Bar<Baz>>.class
BTW, in Java you don't need to prefix access to instance variable with this: if your object defines a url and template members, just access them with their simple name, and not by prefixing like you do this.url and this.template

Java Generics, Create an instance of Class<T>

I am trying to write a generic method for deserializing json into my model. My problem is that I don't know how to get Class from the generic type T. My code looks something like this (and doesn't compile this way)
public class JsonHelper {
public <T> T Deserialize(String json)
{
Gson gson = new Gson();
return gson.fromJson(json, Class<T>);
}
}
I tried something else, to get the type, but it throws an error I had the class as JsonHelper<T> and then tried this
Class<T> persistentClass = (Class<T>) ((ParameterizedType)getClass()
.getGenericSuperclass())
.getActualTypeArguments()[0];
The method signature looks like this
com.google.gson.Gson.fromJson(String json, Class<T> classOfT)
So, how can I translate along T so that when I call JsonHelper.Deserialize<MyObject>(json); I get an instance of the correct object?
You need to get a Class instance from somewhere. That is, your Deserialize() method needs to take a Class<T> as a parameter, just like the underlying fromJson() method.
Your method signature should look like Gson's:
<T> T Deserialize(String json, Class<T> type) ...
Your calls will look like this:
MyObject obj = helper.Deserialize(json, MyObject.class);
By the way, the convention to start method names with a lowercase letter is well established in Java.
Unfortunately, the way Java handles generics, you cannot get the class like you're asking. That's why Google's stuff asks specifically for the class as an argument. You'll have to modify your method signature to do the same.

Categories