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.
Related
I try to write a generic data loader. Here is the loader logic (code snippet 1 (CS1)):
public class Loader<T> {
private ObjectMapper objectMapper = new ObjectMapper();
public T getData(String testDataPath , Class<T> resultClassType) throws IOException {
return objectMapper.readValue(new File(testDataPath), resultClassType);
}
}
And here is the code snipper where I use the loader (code snippet 2 (CS2)):
String[] stringArray;
//Todo something
stringArray = new Loader<String[]>().getData("<path>", String[].class);
My first question is: if I pass the type information here in CS2: new Loader<String[]>() why I can't use this generic information here: return objectMapper.readValue(new File(testDataPath), new T().getClass());?
And at this point I got confused of terms T, Class<> and other type related classes which are allowed to pass as second parameter in the readValue function in objectMapper (Class<>, JavaType, TypeReference, ResolvedType).
So can someone explain me why I can't use the T as I tried and what are the differences between Class<>, JavaType, TypeReference, ResolvedType?
Thx!
T is nothing but the placeholder for the actual type, which will be provided at runtime. Therefore, the compiler has no clue T is, and consequently would not allow you to do things like T.class, or new T(). But compiler can help you to ensure that where you expect to operate with type T you'll really get T, i.e. you code type-safe.
Regarding the Jackson's TypeReference it's useful for Collections and parametrized classes, for instance:
new TypeReference<List<String>() {}
new TypeReference<Foo<Bar>() {}
If you were using Class<T>, you'll not be able to provide information about parameters of the type: Class<List.class>, Class<Foo.class>.
Therefore, Class<T> is handy only when you're parsing a non-generic object of type T.
For more information on Generics, refer to the Java language specification:
§4.5. Parameterized Types.
§4.6. Type Erasure.
I am using GSON to decode JSON into an object of type T e.g.
public T decode(String json) {
Gson gson = new Gson();
return gson.fromJson(json, new TypeToken<T>() {}.getType());
}
This however returns an exception -
java.lang.AssertionError: Unexpected type.
Expected one of:
java.lang.reflect.ParameterizedType,
java.lang.reflect.GenericArrayType,
but got: sun.reflect.generics.reflectiveObjects.TypeVariableImpl, for type token: T
I thought that by using TypeToken I avoided Type Erasure.
Am I wrong?
Thanks
First of all, I fail to see how it's useful to wrap Gson like that.
As to your problem, the information about generic type T itself is not available during runtime. It's been erased. It's only available during compile time. You want to parameterize it with the actual type instead like new TypeToken<List<String>>.
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.
In order to be able to pass the actual type around, you've to reinvent the same thing as TypeToken is already doing. And this makes no sense :) Just reuse it or just use Gson straight without wrapping it in some helper class like that.
I think the first answer is not pointing out the actual solution: you MUST also pass Class instance along with T, like so:
public T decode(String json, Class<T> cls) {
Gson gson = new Gson();
return gson.fromJson(json, cls);
}
This is because 'T' here is a type VARIABLE, not a type reference; and only used by compiler to add implicit casts and verify type compatibility. But if you pass actual class it can be used; and compiler will check type compatibility to reduce chance of mismatch.
Alternatively you could take in TypeToken and pass it; but TypeToken must be constructed with real type, not a type variable; type variable is of little use here. But if you do want to wrap things you wouldn't want caller to use TypeToken (which is a Gson type).
Same wrapping mechanism would work with other libs like Jackson, which you mentioned.
My solution to this was to use the json parser and break it into pieces
public static <TT> PushObj<TT> fromJSON(String json, Class<TT> classType)
{
JsonObject jObj = new JsonParser().parse(json).getAsJsonObject();
String type = jObj.get("type").getAsString();
JsonObject job = jObj.get("object").getAsJsonObject();
TT obj = new Gson().fromJson(job, classType);
return new PushObj<TT>(type, obj);
}
Where the object structure is:
{String:Type, Generic:Object}
And the variables are:
jObj is the JSONObject of the string passed in
and job is the JSONObject of the generic object
So i used the json parser to get the type separately, and reflection for the object.
This is the method:
protected <T> TestPageResult<T> getTestPageResutForRequest(MockHttpServletRequestBuilder request) throws Exception {
String responseJson = mockMvc.perform(request).andReturn().getResponse()
.getContentAsString();
TestPageResult<T> response = getObjectMapper().readValue(responseJson,
new TypeReference<TestPageResult<T>>() {
});
return response;
}
I call it like this:
TestPageResult<SomeDto> pageResult = this.<SomeDto>getTestPageResutForRequest(getRequest());
TestPageResult is:
protected static class TestPageResult<T> {
private List<T> items;
private long totalCount = -1;
public TestPageResult() {
}
//omitted getters and setters
}
The resulting pageResult.getItems() contains a List of LinkedHashMap instead of a list of SomeDto. If I were to just hardcode the SomeDto type in the objectMapper.readValue method I'd get the correct results.
What's the problem?
edit: The suggested duplicated did solve my problem - kind of.
I used:
JavaType type = getObjectMapper().getTypeFactory().constructParametricType(TestPageResult.class, clazz);
TestPageResult<T> response = getObjectMapper().readValue(responseJson, type);
Problem is there is no going around not passing down a Class argument to the method. So the method looks ugly due to both passing a generic type and the same thing as a Class. Obviously you can just not pass the generic now but this way a casting would be required and adding SuppressWarnings and so on.
The problem is erasure. All these <T> parameters don't exist in the compiled code, after they're erased. This means that source new TypeReference<TestPageResult<T>>() looks like new TypeReference<TestPageResult>() once compiled, which is not what you want. (Similar to how a List<String> ends up being a List in compiled code, and it's just compile-time validation that you don't add Integers to your String List.)
I think there's roughly two ways to deal with this (in this case), both of these you already stumbled upon:
Either you create a type that properly represents what you want, such as: new TypeReference<TestPageResult<SomeDto>>(), or class SomeDtoPageResult extends TestPageResult<SomeDto> which you can then use in places like readValue(..., SomeDtoPageResult.class);
Or you create a complete class representation, like you were doing with JavaType
What you really want won't work. Your best bet is to tinker and come up with the cleanest code that solves it. Generics let you express really elaborate structures, and when you serialize an actual instance (nested objects), that comes out just fine, but when the classes need to be introspected at runtime, e.g. for deserialization (your use case) or to build a model (e.g. to generate Swagger docs), this becomes problematic.
My problem is that I'm trying to instantiate a list with a parameter that have the same class as my function's argument :
public <A extends CommunicationObject> List<A> Myfunction(A myObject){
List<A> firstList;
//do something
}
When I call the function :
List<person> persons = Myfunction(person myObject);
The first list take A as CommunicationObject and this is not what I want.
I also tried to do this :
public <A extends CommunicationObject> List<A> Myfunction(A myObject){
List<myObject.getClass()> firstList;
//do something
}
but it is not allowed. Is there any chance that I can fix this ?
Update :
"person" is a subClass of "CumminicationObject". There is some attributes that exists in person and not in CommunicationObject. Actually this is just an example. What I'm trying to do is to convert a JSON to List"<"A">", and A can be "person" or other class that extends CommunicationObject.
The JSON contain the same attributes as the "A" class in List"<"A">".
In order to do the convertion, the parameter "A" in List"<"A">" have to be the same as my object Class in myfunction(A myObject).
Java uses type-erasure, which means your method declaration is somewhat pointless because there's no return type for the compiler to infer the type of A from.
This means that at runtime you've effectively got:
public void Myfunction(CommunicationObject myObject)
This method signature, when you think about it, is what its implementation would have to work with anyway and so your list should be a List<CommunicationObject>.
Following update to the question regarding de-serialization from JSON:
When de-serializing from JSON to a Java object you've got two choices:
declare the type to de-serialize into at the point of de-serialization like GSON does.
infer the type to de-serialize into within the JSON.
Due Java's type-erasure this is the only way to do this.
I'm trying to marshal a list: List<Pojo> objects via the Spring Rest Template.
I can pass along simple Pojo objects, but I can't find any documentation that describes how to send a List<Pojo> objects.
Spring is using Jackson JSON to implement the HttpMessageConverter. The jackson documentation covers this:
In addition to binding to POJOs and
"simple" types, there is one
additional variant: that of binding to
generic (typed) containers. This case
requires special handling due to
so-called Type Erasure (used by Java
to implement generics in somewhat
backwards compatible way), which
prevents you from using something like
Collection<String>.class (which does
not compile).
So if you want to bind data into a
Map<String,User> you will need to use:
Map<String,User> result = mapper.readValue(src, new TypeReference<Map<String,User>>() {});
where TypeReference is only needed to
pass generic type definition (via
anynomous inner class in this case):
the important part is
<Map<String,User>> which defines type
to bind to.
Can this be accomplished in the Spring template? I took a glance at the code and it makes me thing not, but maybe I just don't know some trick.
Solution
The ultimate solution, thanks to the helpful answers below, was to not send a List, but rather send a single object which simply extends a List, such as: class PojoList extends ArrayList<Pojo>. Spring can successfully marshal this Object, and it accomplishes the same thing as sending a List<Pojo>, though it be a little less clean of a solution. I also posted a JIRA in spring for them to address this shortcoming in their HttpMessageConverter interface.
In Spring 3.2 there is now support for generic types using the new exchange()-methods on the RestTemplate:
ParameterizedTypeReference<List<MyBean>> typeRef = new ParameterizedTypeReference<List<MyBean>>() {};
ResponseEntity<List<MyBean>> response = template.exchange("http://example.com", HttpMethod.GET, null, typeRef);
Works like a charm!
One way to ensure that generic type parameters are included is to actually sub-class List or Map type, such that you have something like:
static class MyStringList extends ArrayList<String> { }
and return instance of that list.
So why does this make a difference? Because generic type information is retained in just a couple of places: method and field declarations, and super type declarations. So whereas "raw" List does NOT include any runtime type information, class definition of "MyStringList" does, through its supertype declarations.
Note that assignments to seemingly typed variables do not help: it just creates more compile-time syntactic sugar: real type information is only passed with Class instances (or lib-provided extensions thereof, like JavaType and TypeReference in Jackson's case).
Other than this, you would need to figure out how to pass Jackson either JavaType or TypeReference to accompany value.
If I read the docs for MappingJacksonHttpMessageConverter right, you will have to create and register a subclass of MappingJacksonHttpMessageConverter and override the getJavaType(Class<?>) method:
Returns the Jackson JavaType for the
specific class. Default implementation
returns
TypeFactory.type(java.lang.reflect.Type),
but this can be overridden in
subclasses, to allow for custom
generic collection handling. For
instance:
protected JavaType getJavaType(Class<?> clazz) {
if (List.class.isAssignableFrom(clazz)) {
return TypeFactory.collectionType(ArrayList.class, MyBean.class);
} else {
return super.getJavaType(clazz);
}
}
I have solved this problem by using the following configuration:
private static final String POJO_ARRAY_LIST = PojoArrayList.class.getCanonicalName();
#Bean
public HttpMessageConverter<Object> httpMessageConverter() {
HttpMessageConverter<Object> httpMessageConverter = new MappingJackson2HttpMessageConverter() {
#Override
protected JavaType getJavaType(Type type, #Nullable Class<?> contextClass) {
JavaType javaType;
if (type != null && POJO_ARRAY_LIST.equals(type.getTypeName())) {
ObjectMapper objectMapper = new ObjectMapper();
TypeFactory typeFactory = objectMapper.getTypeFactory();
CollectionType collectionType = typeFactory.constructCollectionType(ArrayList.class, Pojo.class);
javaType = collectionType;
} else {
javaType = super.getJavaType(type, contextClass);
}
return javaType;
}
};
return httpMessageConverter;
}
where PojoArrayList is a final class that extends ArrayList<Pojo>.