When I have a list inside a generic method Gson returns a list of Object instead a list of generic type.
I've seen a lot of threads with no solution, and if I dont use a generic method would have to create a method for each bean.
Is there any one with any idea what do I have to do so solve it?
PS: For a while I've created loop into the list to serialize entity by entity, splitting returned String and deserializing entity by entity, but clearly it's a workaround
Creating a generic list and serializing to JSON (this is a webservices method):
public String listEntity(String nomeClasse) throws WsException {
// Identifying the entity class
Class<?> clazz = Class.forName(nomeClasse);
// Querying with Hibernate
List<?> lst = getDao().listEntity(clazz);
// Check if is null
if (lst == null) {
return "[]";
}
return gson.toJson(lst);
}
Consuming the Webservice method:
public <T> List<T> listEntity(Class<T> clazz)
throws WsIntegracaoException {
try {
// Consuming remote method
String strJson = getService().listEntity(clazz.getName());
Type type = new TypeToken<List<T>>() {}.getType();
// HERE IS THE PROBLEM
List<T> lst = GSON.fromJson(strJson, type);
// RETURNS IS A LIST OF OBJECT INSTEAD OF A LIST OF <T>
return lst;
} catch (Exception e) {
throw new WsIntegracaoException(
"WS method error [listEntity()]", e);
}
}
Invoking the generic method:
List<City> list = listEntity(City.class);
// Here I get a ClassCastException
fillTable(list);
List Element (wrong):
java.lang.Object#23f6b8
Exception:
java.lang.ClassCastException: java.lang.Object cannot be cast to java.io.Serializable
SOLUTION - THIS WORKED FOR ME: Gson TypeToken with dynamic ArrayList item type
public <T> List<T> listEntity(Class<T> clazz)
throws WsIntegracaoException {
try {
// Consuming remote method
String strJson = getService().listEntity(clazz.getName());
JsonParser parser = new JsonParser();
JsonArray array = parser.parse(strJson).getAsJsonArray();
List<T> lst = new ArrayList<T>();
for(final JsonElement json: array){
T entity = GSON.fromJson(json, clazz);
lst.add(entity);
}
return lst;
} catch (Exception e) {
throw new WsIntegracaoException(
"WS method error [listEntity()]", e);
}
}
Related
I want to write a generic function to parse a json string (stringEtcdContent below in the code). The string has a list of objects with the key "value". I parse the json into a tree, get a list of JsonNode's (valueNodes below) with the string to parse using a generic class. The class in which I have the function is this: "public abstract class DashboardReportProvider". Based on a similar question here, I wrote this function:
#SuppressWarnings("unchecked")
public List<T> getStatusList(String path) {
Class<T> clazz;
clazz = (Class<T>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
List<T> statusList = new ArrayList<>();
T statusItem;
ObjectMapper mapper = new ObjectMapper();
try {
String stringEtcdContent = etcdCommandExecutor.getEtcdValue(path);
JsonParser parser=new MappingJsonFactory().createParser(stringEtcdContent);
JsonNode rootNode=parser.readValueAsTree();
List<JsonNode> valueNodes=rootNode.findValues("value");
Iterator<JsonNode> valueNodesIterator=valueNodes.listIterator();
while (valueNodesIterator.hasNext()) {
JsonNode valueNode=(JsonNode)valueNodesIterator.next();
ObjectWriter writer = mapper.writerWithDefaultPrettyPrinter();
String valueString = writer.writeValueAsString(valueNode);
statusItem = mapper.readValue(valueString, clazz);
statusList.add(statusItem);
}
return statusList;
} catch (Exception e) {
LOG.error(e.getMessage());
}
return statusList;
}
It compiles fine, but when I try to run the code, I get this error:
"[ERROR] java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType". What is wrong?
Try ObjectMapper ,
new ObjectMapper().readValue(path,Object.class);
where Object.class - cast to type
Below my function,
public <T> List<T> jsonToListBean(String json, Class<T> clazz) {
Gson gson = new Gson();
Type listType = new TypeToken<List<T>>(){}.getType();
List<T> returnValue = gson.fromJson(json, listType);
return returnValue;
}
but the system returns this exception:
java.lang.ClassCastException: com.google.gson.internal.StringMap
I notice also that if I use the following line code
Type listType = new TypeToken<ArrayList<ShapeBean>>(){}.getType();
it works, but my target is to be absolutly as generic as possible to make my code more efficient.
How should i change my code to make usable also passing the type "T" instead the "ShapeBean"?
Solved. Thanks to kan's link below the code:
public <T> List<T> listEntity(String jsonCommand, Class<T> clazz) {
List<T> lst = new ArrayList<T>();
try {
// Consuming remote method
JsonParser parser = new JsonParser();
JsonArray array = parser.parse(jsonCommand).getAsJsonArray();
for(final JsonElement json: array){
Gson g = new Gson();
T entity = g.fromJson(json, clazz);
lst.add(entity);
}
} catch (Exception e) {
e.printStackTrace();
} return lst;
}
check also this:
Gson TypeToken with dynamic ArrayList item type
I have seen some solution to this problem as long as lists are not involved, so I am pushing my luck in order to see if something can be done.
I'm looking to factorize some heavy duplicated code using generics. I have trouble who are probably related to type-erasure. First of all, here is a sample of duplicated code:
private void readsFoo() throws Exception {
JsonArray jsonArray = getJsonArray(foo_field);
Type listType = new TypeToken<List<Foo>>() {
}.getType();
List<Foo> fooList= gson.fromJson(jsonArray, listType);
for (Foo foo : fooList) {
.....
}
}
private void readsGoo() throws Exception {
JsonArray jsonArray = getJsonArray(goo_field);
Type listType = new TypeToken<List<Goo>>() {
}.getType();
List<Goo> gooList= gson.fromJson(jsonArray, listType);
for (Goo goo : gooList) {
.....
}
}
Now, here is the code I produced myself:
private void readsFoo() throws Exception {
JsonArray jsonArray = getJsonArray(foo_field);
List<Foo> fooList = getElementsList(jsonArray);
for (Foo foo: fooList ) {
.....
}
}
private <T> List<T> getElementsList(JsonArray iArray)
{
Type listType = new TypeToken<List<T>>() {}.getType();
validateJsonElement(listType, "In getElementsList: Unable to find field TypeTokens");
List<T> list = gson.fromJson(iArray, listType);
validateJsonElement(list, "In getElementsList: Unable to find list from Json");
return list;
}
At runtime, I got the following error: java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to ....json.Foo
Is there any way to solve this problem ? Because frankly I do hate not reusable code.
Thanks !
Basically, a TypeToken can not be made with generic type. You could pass it as an argument instead:
private void readsFoo() throws Exception {
JsonArray jsonArray = getJsonArray(foo_field);
List<Foo> fooList = getElementsList(jsonArray, new TypeToken<List<Foo>>(){});
for (Foo foo: fooList ) {
.....
}
}
private <T> List<T> getElementsList(JsonArray iArray, TypeToken<List<T>> tt)
{
Type listType = tt.getType();
validateJsonElement(listType, "In getElementsList: Unable to find field TypeTokens");
List<T> list = gson.fromJson(iArray, listType);
validateJsonElement(list, "In getElementsList: Unable to find list from Json");
return list;
}
You are right about the erasure. Since T is erased, the TypeToken you create will hold the type List<T> instead of List<Foo>, which does not hold any information.
How to do the following properly in Java? I would like to have a generic routine that can create a list of objects. In this routine I expect the constructor of the class of these objects to support a particular parameter.
To clarify: I would like this routine to create a List<T> from a JSON string. This is part of a larger deserialization code. If I can somehow specify that each supported T implements a constructor that creates T given a JSONObject, then I could write the routine something like this:
interface CreatableFromJSONObject<T> {
T(JSONObject object); // Complains about missing return type.
}
static <T> List<T extends CreatableFromJSONObject> jsonArrayToList(JSONArray array) {
List<T> result = new ArrayList<T>();
for (int i = 0; i < array.length; ++i) {
JSONObject jsonObject = array.getJSONObject(i);
result.add(T(jsonObject)); // If T has one constructor with 1 one argument JSONObject
}
return result;
}
and then with an example class that implements this interface
class SomeClass implements CreatableFromJSONObject {
SomeClass(JSONObject object) throws JSONException {
// implementation here
}
}
I could use the desired method as:
List<SomeClass> list = jsonArrayToList<SomeClass>(someJSONArray);
Now, there are quite some hits on this on StackOverflow, so there I have learned that what I outlined above is not possible because Java does not support specifying a particular constructor in an interface and also not for static methods (which would be an alternative route to the same thing and not possible for the same reason).
So, what is then the best way of achieving this?
My current best attempt is the following:
public static <T> List<T> jsonArrayToList(final JSONArray jsonArray, Constructor<T> fromJSONObjectConstructor) {
List<T> result = new ArrayList<T>();
try {
for (int i = 0; i < jsonArray.length(); i++) {
result.add(fromJSONObjectConstructor.newInstance(jsonArray.getJSONObject(i)));
}
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (JSONException e) {
throw new RuntimeException(e);
}
return result;
}
and then to add to each class that should be supported by this method:
public class SomeClass {
public static final Constructor<SomeClass> jsonObjectConstructor;
static {
try {
jsonObjectConstructor = CafellowEntity.class.getConstructor(JSONObject.class);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
SomeClass(JSONObject object) throws JSONException {
// Implementation here
}
}
which I use as
List<SomeClass> list = jsonArrayToList(myJSONArray, SomeClass.jsonObjectConstructor);
It is the most pretty solution I could come up with apart from not using a generic implementation at all and just put the (in this case) couple of lines of code that is actually doing the work in the routine wherever I need them for a particular class.
Any suggestions? What about the performance of this versus alternative solutions? By not supporting it just like this Java is probably telling me I should not be wanting to do this, but that does not prevent me from wondering about it anyway.
Unless you are doing some kind of unusual deserialization this design is overly-complicated and error prone. Android bundles an excellent JSON parser that can already do this, and do it well. Each type that you are currently defining custom constructors for can be deserialized with a single line of code:
final CustomObj obj = new Gson().fromJson(jsonObj.toString(), CustomObj.class);
Incorporating this into your existing method, you end up with this:
public static <T> List<T> jsonArrayToList(final JSONArray jsonArray,
Class<T> clazz) {
if (jsonArray == null || clazz == null)
return null;
final Gson gson = new Gson();
List<T> result = new ArrayList<T>(jsonArray.length());
for (int i = 0; i < jsonArray.length(); i++) {
try {
result.add(gson.fromJson(jsonArray.getJSONObject(i).toString(),
clazz));
} catch (final JSONException je) {
je.printStackTrace();
return null;
}
}
return result;
}
You may simplify your solution by omitting the constructors instances and using the clazz instances instead. So the signature of jsonArrayToList() would be:
public static <T> List<T> jsonArrayToList(final JSONArray jsonArray, Class<T> clazz);
and then calling the constructor by clazz.newInstance();
This would work if you know the type of the class in advance.
I have a method who's definition looks like this:
public static <T> List fromInputStreamToObject(InputStream inputStream, Class<T> clazz) throws Exception{
List objects = null;
try {
if(inputStream!=null){
Reader reader = new BufferedReader(new InputStreamReader(
inputStream));
Type typeOfSrc = new TypeToken<JsonConverter<T>>(){}.getType();
JsonConverter jsonConverter = getGson().fromJson(reader, typeOfSrc);
if(jsonConverter!=null)
objects = jsonConverter.getData();
}
} catch (Exception e) {
throw e;
}
return objects;
}
This doesn't give any compilation error, but
Type typeOfSrc = new TypeToken<JsonConverter<T>>(){}.getType();
gives the run time exception "Type 'T' is not a Class, ParameterizedType, or GenericArrayType. Can't extract class."
How do i use the T to initialize my TypeToken instance?
try this
Type typeOfSrc = type(JsonConverter.class, clazz);
// type(C, A1,...,An) => C<A1,...,An>
static ParameterizedType type(final Class raw, final Type... args)
{
return new ParameterizedType()
{
public Type getRawType(){ return raw; }
public Type[] getActualTypeArguments(){ return args; }
public Type getOwnerType(){ return null; }
};
}
I haven't used GSON in a few months, but as I recall: a Class is a Type. You would just pass your parameter "clazz" in place of the computed "typeOfSrc"