Using Gson, I'm trying to de-serialize a a nested, generic class. The class structure looks like the following:
Wrapper object, simplified, but normally holds other properties such as statusMessage, which are returned along with the data-field from the server:
public class Response<T> {
private List<T> data = null;
public List<T> getData() { return this.data; }
}
Simple class, the expected output from data-field above (though as an array):
public class Language {
public String alias;
public String label;
}
Usage:
Type type = new TypeToken<Response<Language>>() {}.getType();
Response<Language> response = new Gson().fromJson(json, type);
List<Language> languages = response.getData();
Language l = languages.get(0);
System.out.println(l.alias); // Error occurs here
Where the json-variable is something like this.
However, when doing this, I recieve the following exception (on line 3, last code example):
ClassCastException: com.google.gson.internal.StringMap cannot be cast to book.Language
The exception ONLY occurs when storing the data from getData() into a variable (or when used as one).
Any help would be highly appreciated.
The problem you're actually having is not directly due to Gson, it's because of how arrays and Generics play together.
You'll find that you can't actually do new T[10] in a class like yours. see: How to create a generic array in Java?
You basically have two options:
Write a custom deserializer and construct the T[] array there as shown in the SO question I linked above
Use a List<T> instead, then it will simply work. If you really need to return an array, you can always just call List.toArray() in your method.
Edited from comments below:
This is a fully working example:
public class App
{
public static void main( String[] args )
{
String json = "{\"data\": [{\"alias\": \"be\",\"label\": \"vitryska\"},{\"alias\": \"vi\",\"label\": \"vietnamesiska\"},{\"alias\": \"hu\",\"label\": \"ungerska\"},{\"alias\": \"uk\",\"label\": \"ukrainska\"}]}";
Type type = new TypeToken<Response<Language>>(){}.getType();
Response<Language> resp = new Gson().fromJson(json, type);
Language l = resp.getData().get(0);
System.out.println(l.alias);
}
}
class Response<T> {
private List<T> data = null;
public List<T> getData() { return this.data; }
}
class Language {
public String alias;
public String label;
}
Output:
be
Related
I'm using a method that takes a Class<T> as a parameter.
The class I want to pass as a parameter also uses T. It is declared as public class MyObject<T> and has a member declared as public T mMyVar; I then have 2 classes I sometimes use for mMyVar called MyVarObject1 and MyVarObject2.
Example:
private class MyObject<T> {
public T mMyVar;
}
private class MyVarObject1 {
// some variables
}
private class MyVarObject2 {
// some variables
}
Specifically, the method I'm invoking is the JacksonUtil method fromJsonArray.
I'm not sure of the proper syntax here. JacksonUtil needs to know the exact model structure so it can parse the json, but I'm having trouble figuring out the proper syntax for this line:
MyObject<MyVarObject1> result = JacksonUtil.fromJsonArray(jsonStr, MyObject<MyVarObject1>.class);
What I have there doesn't work. My IDE selects the second parameter and says, "Cannot select from parameterized type."
I had a same problem while using with retrofit, This is my solution -
public class ResponseDS<T> {
public int s;
public String e;
public T d;
}
And if you need array of object then,
public class ResponseDSs<T> {
public int s;
public String e;
public T[] d;
}
And below is how I am using it for Retrofit -
Call<ResponseDS<UserDS>> userModelCall = ZivaUtils.getRetrofit().getUser();
I think you have the same problem, hope my solution will help you :)
I do TypedToken from Gson to parse custom objects, I think you can find something similar to use with Jackson, i will edit my answer if i find something later.
You may use TypeToken to load the json string into a custom object.
Gson gson = new Gson();
//This is an example, you probably get this from your server as Json String
MyObject<MyObject1> user = new MyObject<MyObject1>();
String myObjectAsString = gson.toJson(user);
//then parse into your custom object
MyObject other = gson.fromJson(myObjectAsString, new TypeToken<MyObject<MyObject1>>(){}.getType());
I'm using Jackson to deserialize feeds and pull specific values from it. I'm trying to do this without know what feeds will be selected at runtime. I attempted to make a generic class that contains the method for deserializing the feed, and it works oddly enough. The problem is I can't pull any information from it without explicitly stating which class I am pulling from.
public class Motor<T> {
final ObjectMapper jsonMapper = new ObjectMapper();
public Object pullJson( String url, Object blah ) throws IOException{
#SuppressWarnings("unchecked")
T mapped = jsonMapper.readValue(new URL(url), (Class<T>) blah);
return mapped;
}
//Some other code
}
public class Root{
public static String value;
public String getValue(){
return value;
}
}
public class Propeller {
public static void main(String[] args) throws IOException{
Motor<?> motor = new Motor();
List<Class<?>> classes = new ArrayList<Class<?>>();
classes.add(Root.class)
String url = "blah.com/blah.json"
Object blah = classes.get(0);
blah = motor.pullJson(url, blah);
System.out.println(blah.value);
}
}
The only way I could think to pass a class into the method without know what the class will be was to set the class parameter as an Object and then pass a generic class onto it.
If I we're to ask for Root.value, it'll give me value's value, but I won't know what class the method will act on at runtime. And trying to act on the class declared as blah will tell me value cannot be resolved. I have a feeling my thought process here is extremely flawed in trying to pass a class as an object and reading the values that way.
I am using Jackson to parse object. sometime I need list of objects.
when I am using like this its working
List<MyObject> mapper.readValue(file , new TypeReference<MyObject>() {})
but when I am using it like this its not working
public class JsonMocksRepository<T>{
public T getObject() throws Exception{
return mapper.readValue(file ,new TypeReference<T>());
}
}
What I need to do ?
Basically I want to use generics to get the right class
This is because of type erasure. There is no information about the actual type represented by T available at runtime, so your TypeReference will be effectively be simply TypeReference<Object>.
If you want a generic instance of JsonMocksRepository, you will need to inject the TypeReference at construction time:
public class JsonMocksRepository<T>{
private final TypeReference<T> typeRef;
public JsonMocksRepository(TypeReference<T> typeRef) {
this.typeRef = typeRef;
}
public T getObject() throws Exception{
return mapper.readValue(file, typeRef);
}
}
Using Jersey I'm defining a service like:
#Path("/studentIds")
public void writeList(JsonArray<Long> studentIds){
//iterate over studentIds and save them
}
Where JsonArray is:
public class JsonArray<T> extends ArrayList<T> {
public JsonArray(String v) throws IOException {
ObjectMapper objectMapper = new ObjectMapper(new MappingJsonFactory());
TypeReference<ArrayList<T>> typeRef = new TypeReference<ArrayList<T>>() {};
ArrayList<T> list = objectMapper.readValue(v, typeRef);
for (T x : list) {
this.add((T) x);
}
}
}
This works just fine, but when I do something more complicated:
#Path("/studentIds")
public void writeList(JsonArray<TypeIdentifier> studentIds){
//iterate over studentIds and save them by type
}
Where the Bean is a simple POJO such as
public class TypeIdentifier {
private String type;
private Long id;
//getters/setters
}
The whole thing breaks horribly. It converts everything to LinkedHashMap instead of the actual object. I can get it to work if I manually create a class like:
public class JsonArrayTypeIdentifier extends ArrayList<TypeIdentifier> {
public JsonArrayTypeIdentifier(String v) throws IOException {
ObjectMapper objectMapper = new ObjectMapper(new MappingJsonFactory());
TypeReference<ArrayList<TypeIdentifier>> typeRef = new TypeReference<ArrayList<TypeIdentifier>>(){};
ArrayList<TypeIdentifier> list = objectMapper.readValue(v, typeRef);
for(TypeIdentifier x : list){
this.add((TypeIdentifier) x);
}
}
}
But I'm trying to keep this nice and generic without adding extra classes all over. Any leads on why this is happening with the generic version only?
First of all, it works with Longs because that is sort of native type, and as such default binding for JSON integral numbers.
But as to why generic type information is not properly passed: this is most likely due to problems with the way JAX-RS API passes type to MessageBodyReaders and MessageBodyWriters -- passing java.lang.reflect.Type is not (unfortunately!) enough to pass actual generic declarations (for more info on this, read this blog entry).
One easy work-around is to create helper types like:
class MyTypeIdentifierArray extends JsonArray<TypeIdentifier> { }
and use that type -- things will "just work", since super-type generic information is always retained.
I have a static builder method for a class "Model" that takes a JSON string and returns an ArrayList of Models. I would like it refer to the Model's constructer generically so that subclasses can inherit the builder method.
public class Model
{
protected int id;
public Model(String json) throws JSONException
{
JSONObject jsonObject = new JSONObject(json);
this.id = jsonObject.getInt("id");
}
public static <T extends Model> ArrayList<T> build(String json) throws JSONException
{
JSONArray jsonArray = new JSONArray(json);
ArrayList<T> models = new ArrayList<T>(jsonArray.length());
for(int i = 0; i < jsonArray.length(); i++)
models.add( new T(jsonArray.get(i)) )
return models;
}
}
This is a simplified implementation of the class, the relevant line being
models.add( new T(jsonArray.get(i)) )
I know this isn't possible, but I would like to write something that calls the constructor of whatever type T happens to be. I have tried to use this(), which obviously doesn't work because the method "build" is static and I've tried to use reflection to determine the class of T but have been at a loss to figure out how to get it to work. Any help is greatly appreciated.
Thanks,
Roy
The workaround for "dynamic instantiation" with generics is to pass a hint to the class or the method:
public class Model<T> {
Class<T> hint;
public Model(Class<T> hint) {this.hint = hint;}
public T getObjectAsGenericType(Object input, Class<T> hint) throws Exception {
return hint.cast(input);
}
public T createInstanceOfGenericType(Class<T> hint) throws Exception {
T result = hint.newInstance();
result.setValue(/* your JSON object here */);
return result;
}
}
I'm happy to provide more help/ideas but I'm not sure what you want to achieve with your technical solution.
(Note: Example has some over-simplified Exception handling)
The way it is written now, I can't see that the T type parameter in build() is of any use whatsoever. Can't you just drop it and use Model in its place? If so, that would solve your construction problem.