I'm writing a class which will connect to a server and based on some arguments, retrieve a json-string which will be parsed with GSON to the specified (via generics) class.
A stripped down version of the class in charge looks like this:
class Executor<T> {
private Response<T> response;
public void execute() {
Type responseType = new TypeToken<Response<T>>() {}.getType();
this.response = new Gson().fromJson(json, responseType);
}
public Response<T> getResponse() { return this.response; }
}
(the JSON-variable looks like this.)
The class which stores the data once de-serialized looks like this:
class Response<T> {
private List<T> data = null;
public List<T> getData() { return this.data; }
}
The class which the data is trying to be de-serialized to:
public class Language {
public String alias;
public String label;
}
And the code which runs utilizes the classes above:
Executor<Language> executor = new Executor<Language();
List<Language> languages = executor.execute().getResponse().getData();
System.out.println(languages.get(0).alias); // exception occurs here
Which results in the following exception
ClassCastException: com.google.gson.internal.StringMap cannot be cast to sunnerberg.skolbibliotek.book.Language
Any help or suggestions are greatly appreciated!
The short answer is that you need to move the creation of the TypeToken out of Executor, bind the T in Response<T> when you create the token (new TypeToken<Response<Language>>() {}), and pass in the type token to the Executor constructor.
The long answer is:
Generics on a type are typically erased at runtime, except when the type is compiled with the generic parameter bound. In that case, the compiler inserts the generic type information into the compiled class. In other cases, that is not possible.
So for instance, consider:
List<Integer> foo = new ArrayList<Integer>();
class IntegerList extends ArrayList<Integer> { ... }
List<Integer> bar = new IntegerList();
At runtime, Java knows bar contains integers because the Integer type is bound to ArrayList at compile time, so the generic type information is saved in the IntegerList class file. However, the generic type information for foo is erased, so at runtime it is not really possible to determine that foo is supposed to contain Integers.
So it often comes up that we need generic type information in a situation where it normally would be erased before runtime, such as here in the case of parsing JSON data in GSON. In these situations, we can take advantage of the fact that type information is preserved when it is bound at compile-time (as in the IntegerList example above) by using type tokens, which are really just tiny anonymous classes that conveniently store generic type information.
Now to your code:
Type responseType = new TypeToken<Response<T>>() {}.getType();
In this line of your Executor class, we create an anonymous class (inheriting from TypeToken) which has the type Response<T> hard coded (bound) at compile-time. So at runtime, GSON is able to determine that you want an object of Response<T>. But it doesn't know what T is, because you didn't specify it at compile-time! So GSON cannot determine what type will be in the List of the Response object it creates, and it just creates a StringMap instead.
The moral of the story is that you need to specify that T at compile-time. If Executor is meant to be used generically, you probably need to create the type token outside of that class, in your client code. Something like:
class Executor<T> {
private TypeToken<Response<T>> responseType;
private Response<T> response;
public Executor(TypeToken<Response<T>> responseType) {
this.responseType = responseType;
}
public void execute() {
this.response = new Gson().fromJson(json, responseType.getType());
}
public Response<T> getResponse() { return this.response; }
}
// client code:
Executor<Language> executor = new Executor<Language>(new TypeToken<Response<Language>>() { });
executor.execute();
List<Language> languages = executor.getResponse().getData();
System.out.println(languages.get(0).alias); // prints "be"
By the way, I did test the above on my machine.
Sorry if that was too long!
You haven't called response.execute(); after Executor<Language> executor = new Executor<Language>(); this statement. You can't utilize the java generics here, but you can get the same effect with the following code.
Response.java
import java.io.Serializable;
import java.util.List;
/**
*
* #author visruth
*/
public class Response<T> implements Serializable {
private List<T> data = null;
public List<T> getData() {
return this.data;
}
public void setData(List<T> data) {
this.data = data;
}
}
Language.java
import java.io.Serializable;
/**
*
* #author visruth
*/
public class Language implements Serializable {
private String alias;
private String label;
public String getAlias() {
return alias;
}
public void setAlias(String alias) {
this.alias = alias;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
}
Finally, the Executor.java
import com.google.gson.Gson;
import java.util.*;
/**
*
* #author visruth
*/
public class Executor<T> {
private Response<T> response;
public Response<T> getResponse() {
return response;
}
/**
* #param args the command line arguments
*/
public void executor() {
//sample data
Response<Language> response=new Response<Language>();
Language lan1=new Language();
lan1.setAlias("alias1");
lan1.setLabel("label1");
Language lan2=new Language();
lan2.setAlias("alias2");
lan2.setLabel("label2");
List<Language> listOfLangauges=new ArrayList<Language>();
listOfLangauges.add(lan1);
listOfLangauges.add(lan2);
response.setData(listOfLangauges);
Gson gson=new Gson();
String json = gson.toJson(response);
System.out.println(json);
Response<Language> jsonResponse = gson.fromJson(json, Response.class);
List list=jsonResponse.getData();
List<Language> langs=new ArrayList<Language>();
for(int i=0; i<list.size(); i++) {
Language lan=gson.fromJson(list.get(i).toString(), Language.class);
langs.add(lan);
//System.out.println(lan.getAlias());
}
Response<Language> responseMade=new Response<Language>();
responseMade.setData(langs);
this.response=(Response<T>) responseMade;
}
}
You can test it as follows
Executor<Language> executor = new Executor<Language>();
executor.executor();
List<Language> data = executor.getResponse().getData();
for(Language langu: data) {
System.out.println(langu.getAlias());
System.out.println(langu.getLabel());
}
Your problem is linked to java Type erasure. Generics are only known at compile time.
So sadly, there is no way for GSON to know to which class it should deserialize, using reflection.
Related
I have been working on this solution for months and I have come to the conclusion that there is no clean way to achieve what I am trying to achieve. I feel as though my education in polymorphism is failing me, so I've come to StackOverflow to get a second opinion. Sorry if this seems long and convoluted. That's been my brain for the past couple of months and at this point I'm out of ideas. I'm hoping somebody can take a look and see that I could've avoided all this mess by doing it some other way.
What I am trying to achieve is two generic classes: One that can represent any "saveable" object, and one that can represent a list of saveable objects (or what I call a "store"). A saveable object can save itself using GSON, and a store can also save itself using GSON to a JSON file. The difference being that saveable objects are generically representing any GSON object that can be saved, whereas stores are extending from saveables to become a saveable hash map of objects via IDs.
An example output I am looking for is as so:
Imagine I have an object with a uuid string field and a name string field. I want to be able to create a Store, which is a LinkedHashMap, of these objects, but also extend a Saveable to allow the objects to be saved as so:
test.json
{"dbf39199209e466ebed0061a3491ed9e":{"uuid":"dbf39199209e466ebed0061a3491ed9e","name":"Example Name"}}
I would also like to be able to load this JSON back into the objects via the Store's load method.
An example code usage would be like so:
Store<User> users = new Store<>();
users.load();
users.add(new User("dbf39199209e466ebed0061a3491ed9e", "Example Name"));
users.save();
My Attempts
Saveables
What I expect a "saveable" object to be able to do is as follows: provide a non-argumented method for saving and provide a non-argumented method for loading. A saveable object represents any object that can be saved via GSON. It contains two fields: a Gson gson object and a Path location. I provide those in the constructor of my saveable. I then want to provide two methods: a Saveable#save() method and a Saveable#load() method (or a static Saveable#load() method, I am indifferent). The way you use a Saveable object is by extending it (so it is abstract) to another object representing something, say, TestSaveable, and then the usage is as so:
TestSaveable saveable = new TestSaveable(8);
saveable.save(); // Saves data
saveable.setData(4);
saveable = saveable.load(); // Loads old data
I also would like a saveable object to be able to handle a generic, such as an integer (think of the last example but with an integer generic). This would allow me to execute my next plan for Stores.
My attempt at an implementation was the following:
public abstract class Saveable {
private transient Gson gson;
private transient Path location;
public Saveable(Gson gson, Path location) {
this.gson = gson;
this.location = location;
}
#SuppressWarnings("unchecked")
public <T extends Saveable> T save() throws IOException {
if (location.getParent() != null) {
Files.createDirectories(location.getParent());
}
Files.write(location, gson.toJson(this).getBytes(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, LinkOption.NOFOLLOW_LINKS);
return (T) this;
}
protected <T extends Saveable> T load(Class<T> clazz, #NotNull Class<?>... generics) throws IOException {
if (!Files.exists(location)) {
return this.save();
} else {
InstanceCreator<Saveable> creator = type -> this;
Type type = TypeToken.getParameterized(clazz, generics).getType();
Gson newGson = gson.newBuilder().registerTypeAdapter(type, creator).create();
return newGson.fromJson(Files.newBufferedReader(location), type);
}
}
}
Unfortunately, this attempt failed in my goal, because upon making my TestSaveable class users still had to pass the generic through for loading:
public class TestSaveable<T> extends Saveable {
public boolean testBool = false;
public T value;
public TestSaveable(T value) {
super(new Gson(), Path.of("test.json"));
this.value = value;
}
public final TestSaveable<T> load(Class<T> generic) throws IOException {
return super.load(TestSaveable.class, generic);
}
}
However, through this I did get a fairly clean implementation with the exception of little to no type checking at all and constantly having to add supressions for it:
public class Test {
public static void main(String[] args) {
try {
TestSaveable<Integer> storeB4 = new TestSaveable<>(5).save();
storeB4.value = 10;
TestSaveable<Integer> store = storeB4.load(Integer.class);
System.out.println("STORE: " + store);
} catch (Exception e) {
e.printStackTrace();
}
}
}
Stores
Stores are an extension of saveables. A store is a LinkedHashMap which will quickly and easily save all of the objects in it as a map in GSON. Unfortunately, I'm not even sure where to start on this. I cannot extend two objects (the two being a LinkedHashMap<String, T> and a Saveable), but I also cannot use interfaces for the Saveable object.
I previously tried the following using the IStorable and ISaveable classes as an alternative to the abstract Saveable class I've shown you above, but this resulted in another very ugly and non-robust solution to my issue.
Saveable.java
public class Saveable {
// Suppress default constructor
private Saveable() {}
// Save a class to the specified location using the specified gson
public static <T extends ISaveable> T save(T instance) throws IOException {
Files.createDirectories(instance.getLocation().getParent());
Files.write(instance.getLocation(), instance.getGson().toJson(instance).getBytes(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, LinkOption.NOFOLLOW_LINKS);
return instance;
}
// Load a file from the specified location using the specified gson and cast it to the specified class using the specified generic
public static <T extends ISaveable> ISaveable load(Path location, Gson gson, Class<T> clazz, Class<?> genericClazz) throws IOException {
if (!Files.exists(location)) {
return null;
} else {
TypeToken<?> type = genericClazz == null ? TypeToken.get(clazz) : TypeToken.getParameterized(clazz, genericClazz);
ISaveable saveable = gson.fromJson(Files.newBufferedReader(location), type.getType());
saveable.setGson(gson);
saveable.setLocation(location);
return saveable;
}
}
}
ISaveable.java
public interface ISaveable {
// Gson
Gson getGson();
void setGson(Gson gson);
// Location
Path getLocation();
void setLocation(Path location);
}
IStorable.java
public interface IStoreable {
String getUuid();
}
Store.java
public class Store<T extends IStoreable> extends LinkedHashMap<String, T> implements ISaveable {
private transient Path location;
private transient Gson gson;
public Store(Path location, Gson gson) {
this.location = location;
this.gson = gson;
}
public Store() {
this.location = null;
this.gson = null;
}
public Store<T> put(T value) {
this.put(value.getUuid(), value);
return this;
}
public Store<T> remove(T value) {
this.remove(value.getUuid());
return this;
}
public Store<T> save() throws IOException {
return Saveable.save(this);
}
#SuppressWarnings("unchecked")
public static <T extends IStoreable> Store<T> load(Path location, Gson gson, Class<T> genericClazz) throws IOException {
ISaveable saveable = Saveable.load(location, gson, Store.class, genericClazz);
if (saveable == null) {
return new Store<T>(location, gson).save();
} else {
return (Store<T>) saveable;
}
}
}
This solution achieved me almost the result I was looking for, but fell short quickly on the loading process as well as just not being a robust solution, excluding the hundreds of Java practices I'm sure to have ruined at this point:
Store<ExampleStoreable> store = Store.load(Paths.get("storetest.json"), new Gson(), ExampleStoreable.class);
store.put(new ExampleStoreable("Example Name"));
store.save();
And before I get any comments saying I shouldn't be posting this on StackOverflow: if not here, where else? Please help point me in the right direction, I'd love to not be left in the dark.
Thanks if anyone is able to help and if not I understand. This isn't the easiest question by any means.
I was extremely close to the correct solution, but my logic just wasn't lining up.
The fixed load method is as follows:
default <T extends ISaveable> T load() throws IOException {
if (!Files.exists(getLocation())) {
return save();
} else {
InstanceCreator<?> creator = type -> (T) this;
Gson newGson = getGson().newBuilder().registerTypeAdapter(getType(), creator).create();
return newGson.fromJson(Files.newBufferedReader(getLocation()), getType());
}
}
Instead of attempting to prevent type erasure, and instead of passing the class every time we call the method, we just... pass it in the constructor. It was really that simple. I don't care about sending the type through the constructor, as long as .load() and .save() do not result in hundreds of lines of repetitive code.
I can't believe I was this close to the solution the whole time. It's incredible to me how simple this was. Guess that's the life of programming, right?
Here is the full class, which I determined was better as an interface called ISaveable.java:
public interface ISaveable {
Type getType();
Gson getGson();
Path getLocation();
/**
* Saves this object.
*
* #param <T> The extended object to cast to.
* #return The object after having been saved.
* #throws IOException Thrown if there was an exception while trying to save.
*/
#SuppressWarnings("unchecked")
default <T extends ISaveable> T save() throws IOException {
Path location = getLocation().toAbsolutePath();
if (location.getParent() != null) {
Files.createDirectories(location.getParent());
}
Files.write(getLocation(), getGson().toJson(this).getBytes(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, LinkOption.NOFOLLOW_LINKS);
return (T) this;
}
/**
* Loads this object.
*
* #param <T> The extended object to cast to.
* #return The object after loading the new values.
* #throws IOException Thrown if there was an exception while trying to load.
*/
#SuppressWarnings("unchecked")
default <T extends ISaveable> T load() throws IOException {
if (!Files.exists(getLocation())) {
return save();
} else {
InstanceCreator<?> creator = type -> (T) this;
Gson newGson = getGson().newBuilder().registerTypeAdapter(getType(), creator).create();
return newGson.fromJson(Files.newBufferedReader(getLocation()), getType());
}
}
}
An example implementation:
public class ExampleSaveable implements ISaveable {
private boolean testBoolean = false;
private String myString;
public ExampleSaveable(String myString) {
this.myString = myString;
}
#Override
public Gson getGson() {
return new Gson();
}
#Override
public Type getType() {
return TypeToken.get(ExampleSaveable.class).getType();
}
#Override
public Path getLocation() {
return Path.of("test.json");
}
}
And an example usage is like so:
ExampleSaveable saveable = new ExampleSaveable("My Data!").load();
saveable.myString = "This is a replacement string!";
saveable.save();
On the first run, the output is "My Data!", on the second, the output is "This is a replacement string!"
The corresponding output JSON was:
{"testBoolean":false,"myString":"This is a replacement string!"}
This allowed me to subsequently extend the class to create my Store.
IStorable.java
public interface IStorable {
String getUuid();
}
Store.java
public class Store<T extends IStorable> extends LinkedHashMap<String, T> implements ISaveable {
// GSON & Location
private transient Gson gson;
private transient Path location;
private transient Type type;
/**
* Constructs a new store.
*
* #param gson The gson to use for saving and loading.
* #param location The location of the JSON file.
* #param generic The generic that this instance of this class is using (due to type erasure).
*/
public Store(Gson gson, Path location, Class<T> generic) {
this.gson = gson;
this.location = location;
this.type = TypeToken.getParameterized(Store.class, generic).getType();
}
// Putting
public Store<T> put(T value) {
this.put(value.getUuid(), value);
return this;
}
public Store<T> putAll(T... values) {
for (T value : values) {
this.put(value.getUuid(), value);
}
return this;
}
// Replacing
public Store<T> replace(T value) {
this.replace(value.getUuid(), value);
return this;
}
// Removing
public Store<T> remove(T value) {
this.remove(value.getUuid());
return this;
}
// Implement ISaveable
#Override
public Gson getGson() {
return gson;
}
#Override
public Path getLocation() {
return location;
}
#Override
public Type getType() {
return type;
}
// Setters
public void setLocation(Path location) {
this.location = location;
}
}
I want to create a generic method to return deserialized data. The idea is to pass through parameter a Type.class and when it deserialize using Gson, it returns a collection or a single object from Type.
For example:
public class Client {
String id;
String name;
/* getters and setters */
}
public class Account {
String number;
String bank;
/* getters and setters */
}
public class Main {
public static void main(String[] args) {
List<Client> clients = Utils.getList(Client.class, "");
Account account = Utils.getSingle(Account.class, "number = '23145'");
}
}
public class Utils {
public static Class<? extends Collection> getList(Class<?> type, String query) {
//select and stuff, works fine and returns a Map<String, Object> called map, for example
Gson gson = new Gson();
JsonElement element = gson.fromJsonTree(map);
//Here's the problem. How to return a collection of type or a single type?
return gson.fromJson(element, type);
}
public static Class<?> getSingle(Class<?> type, String query) {
//select and stuff, works fine and returns a Map<String, Object> called map, for example
Gson gson = new Gson();
JsonElement element = gson.fromJsonTree(map);
//Here's the problem. How to return a collection of type or a single type?
return gson.fromJson(element, type);
}
}
How can I return a single object from Type or a list of it?
First of all you need change method signature to generic type:
public static <T> List<T> getList(Class<T> type, String query)
and
public static <T> T getSingle(Class<T> type, String query)
getSingle method should start working, but for method getList you need to change implementation:
create Type listType = new TypeToken<List<T>>() {}.getType();
return gson.fromJson(element, listType);
you can find more information about com.google.gson.Gson from javaDoc
So, lets say I have an enum, "Data".
public enum Data {
FIRSTNAME(String.class, "John");
private final Class<?> defaultClass;
private final Object defaultData;
Data(Class<?> clazz, Object data) {
this.defaultClass = clazz;
this.defaultData = data;
}
public Class<?> getDataClass() {
return this.defaultClass;
}
}
Would it be possible to create a method that gets its return type based on the passed Data enum's getDataClass() response? Ie like this:
//This code obviously won't work, it's just another way of showing this.
public [data.getDataClass()] getData(Data data) {
//Return the data.
}
If I have a class with a generic like this:
public class ResponseSimple<T> {
private Map<String, Collection<String>> headers;
private int status;
private T body;
}
Then,in other class I have a method which I need to use an instance of this class, but the method passes by param a java.lang.reflect.Type and it's overrided so I can't change the any of the method (name, signature..):
public class ResponseEncoder extends GsonDecoder {
public ResponseEncoder() {
super();
}
#Override
public Object decode(Response response, Type type) throws IOException
{
//How assign type T using type param??
//¿ResponseSimple<T> responseSimple = new ResponseSimple();?
return null;
}
}
How could I assign the generic type T using the param type (java.lang.reflect.Type)?
I would suggest something like this:
#Override
public <T> T decode(Response response, Class<T> type) throws IOException
{
//How assign type T using type param??
ResponseSimple<T> response = new ResponseSimple<T>();
return response;
}
Then use decode as follows:
.decode(response, NameOfClass.class)
Edit:
If you need to extend your class you could use a static helper function:
public static <T> ResponseSimple<T> createResponse(Class<T> clazz)
{
return new ResponseSimple<>();
}
And use it like this:
public class ResponseEncoder extends GsonDecoder {
public ResponseEncoder() {
super();
}
#Override
public Object decode(Response response, Type type) throws IOException
{
Class<?> clazz = (Class<?>) type;
ResponseSimple<?> response = createResonse(clazz);
return null;
}
}
I hope I understood your question correctly.
To create a new instance of your generic class you need to infer the correct type arguments like this if you want your ResponseSimple<T> to contain java.lang.reflect.Type:
ResponseSimple<Type> response = new ResponseSimple<>();
So, inbetween the <> you need to add the name of the class you want to use.
Also have a look at this: https://docs.oracle.com/javase/tutorial/java/generics/types.html
//EDIT:
As you said you want to infer the type arguments dynamically, what you did works fine for me. The only thing is that you forgot the diamond operator:
#Override
public Object decode(Response response, T type) throws IOException
{
ResponseSimple<T> response = new ResponseSimple<>(); //<> in the second part
return null;
}
I'm trying to do something like this :
public class ResponseProcessorFactory {
public static <T> ResponseProcessor<T> newResponseProcessor(){
return new GsonResponseProcessor<T>();
}
}
public class GsonResponseProcessor<T> implements ResponseProcessor<T> {
protected T response;
protected TypeToken typeToken;
public GsonResponseProcessor() {
this.typeToken = new TypeToken<T>(){};
}
#Override
public void parse(String jsonString) throws JSONException, IOException {
response = GsonHelper.getGsonInstance().fromJson(jsonString, typeToken.getType());
}
public T getResponse() {
return response;
}
}
private void ResponseProcessor getResponseProcessor(){
return ResponseProcessorFactory<List<String>>.newResponseProcessor();
}
Now, whenever I invoke getResponseProcessor(), it doesn't return me the response processor for List<String>. Rather, it returns the default response processor for Object.
I'm sure, I'm missing some concept regarding generic. Can someone explain in detail ?
EDIT :
The real usage is like this :
public BaseRequestWithResponseProcessor<List<Dashboard>> getDashboards(Listener<List<Dashboard>> responseListener, ErrorListener errorListener) {
String url = mBaseUrl + "/dashboard";
ResponseProcessor<List<Dashboard>> responseProcessor = ResponseProcessorFactory.newResponseProcessor();
AuthInfo authInfo = getAuthInfo();
BaseRequestWithResponseProcessor<List<Dashboard>> request = new BaseRequestWithResponseProcessor<List<Dashboard>>(
Method.GET, url, authInfo, null, responseProcessor, responseListener, errorListener);
return request;
}
In the GsonResponseProcessor constructor type erasure has happened and at runtime only one version of the method will exist with the type variable T converted to Object.
In Java only one version of generic methods and classes will exist, the type parameters only exist during compile-time and will be replaced by Object during run-time.
Type tokens must be constructed with a concrete type to capture the type information. This is the whole point with them, to capture type information at a place where the concrete type is known. The token can then be stored in variables and later be used to lookup objects or get hold of the type information with reflection.
The solution here is that the caller of getResponseProcessor who knows the concrete type creates the type token and passes it as a parameter. You could also pass in a Class object if that works in you situation. If you want to use generic classes as tokens however, as in your example with List<Dashboard> you will need a type token.
Something like this:
ResponseProcessor<List<String>> p = ResponseProcessorFactory.newResponseProcessor(new TypeToken<List<String>>() {});
You can work around the type erasure by passing in the class type as method parameter.
public class ResponseProcessorFactory {
public static <T> ResponseProcessor<T> newResponseProcessor(Class<T> type){
return new GsonResponseProcessor<T>(type);
}
}
public class GsonResponseProcessor<T> implements ResponseProcessor<T> {
protected T response;
protected TypeToken typeToken;
public GsonResponseProcessor(Class<T> type) {
this.typeToken = TypeToken.get(type);//depends on the API version
//this.typeToken = new TypeToken<T>(type);
//this.typeToken = TypeToken.of(type);
}
#Override
public void parse(String jsonString) throws JSONException, IOException {
response = GsonHelper.getGsonInstance().fromJson(jsonString, typeToken.getType());
}
public T getResponse() {
return response;
}
}
Have you tried changing the signature to the correct type, too?
private ResponseProcessor<List<String>> getResponseProcessor() {
return ResponseProcessorFactory.newResponseProcessor();
}