google gson LinkedTreeMap class cast to myclass - java

I knew this question has been asked before. Due to my novice skill in java and android. I can't resolve this issue for more than a week.
One of my friend and i developing an android project were there are a couple of things like this.
The most weird part of this things is, it's happening only from when i download and test it from Google play store. Not from local android studio installation or debug mode.
What could be the problem here, or this returning list which is totally wrong ?
My friend convincing that this code returns correctly but from play store installation it's always an error.
Please suggest me where should i keep digging?
#Override
public void promiseMethod(JSONObject object) {
if (object != null) {
if (object.has(DO_SERVICES)) {
vehicleDetails = new ArrayList < Object[] > (1);
List < String > vehicleNoList = new ArrayList < String > (1);
List < String > serviceList = new ArrayList < String > (1);
try {
Gson gson = new Gson();
JSONObject jsonObj = new JSONObject(object.get(DO_SERVICES)
.toString());
servDto = gson.fromJson(jsonObj.toString(),
ServiceDto.class);
if (servDto.getServiceDto() instanceof List) {
List < DoServiceDto > doServiceList = servDto.getServiceDto();
Exception is
java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to com.gaurage.dto.DoServiceDto
at com.gaurage.user.User_Test_Main.promiseMethod(Unknown Source)

Serializing and Deserializing Generic Types
When you call toJson(obj), Gson calls obj.getClass() to get information on the fields to serialize. Similarly, you can typically pass MyClass.class object in the fromJson(json, MyClass.class) method. This works fine if the object is a non-generic type. However, if the object is of a generic type, then the Generic type information is lost because of Java Type Erasure. Here is an example illustrating the point:
class Foo<T> { T value;}
Gson gson = new Gson();
Foo<Bar> foo = new Foo<Bar>();
gson.toJson(foo); // May not serialize foo.value correctly
gson.fromJson(json, foo.getClass()); // Fails to deserialize foo.value as Bar
The above code fails to interpret value as type Bar because Gson invokes list.getClass() to get its class information, but this method returns a raw class, Foo.class. This means that Gson has no way of knowing that this is an object of type Foo, and not just plain Foo.
You can solve this problem by specifying the correct parameterized type for your generic type. You can do this by using the TypeToken class.
Type fooType = new TypeToken<Foo<Bar>>() {}.getType();
gson.toJson(foo, fooType);
gson.fromJson(json, fooType);
I have Parent class and it's child class some of them having List types in it. Like this parent class i have 30 files. I solved it like this.
Gson gson = new Gson();
JSONObject jsonObj = new JSONObject(object.get(DO_SERVICES).toString());
Type type = new TypeToken<MyDto>() {}.getType();
servDto = gson.fromJson(jsonObj.toString(),type);
The most important thing is, I can't reproduce this error in local testing from Android studio. This problem pops up only, When i generate signed apk and publish app into PlayStore were the app stops, and the report says Cannot cast LinkedTreeMap to myclass.
It was hard for me to reproduce the same result in my local testing (includes Debug mode).

EngineSense's answer is correct.
However, if you still want to use generics and don't want to pass in the concrete class type as a parameter here's an example of a workaround in Kotlin.
(Note that inline methods with reified type params cannot be called from Java).
May not be the most efficient way to get things done but it does work.
The following is in GsonUtil.kt
inline fun <reified T> fromJson(json: String): T? {
return gson.fromJson(json, object : TypeToken<T>() {}.type)
}
fun <T> mapToObject(map: Map<String, Any?>?, type: Class<T>): T? {
if (map == null) return null
val json = gson.toJson(map)
return gson.fromJson(json, type)
}
Method that retrieves a lightweight list of generic objects.
inline fun <reified T: MyBaseClass> getGenericList(): List<T> {
val json = ...
//Must use map here because the result is a list of LinkedTreeMaps
val list: ArrayList<Map<String, Any?>>? = GsonUtil.fromJson(json)
//handle type erasure
val result = list?.mapNotNull {
GsonUtil.mapToObject(it, T::class.java)
}
return result ?: listOf()
}

Source
In My ListView BaseAdapter facing same issue
JSON format to show
{
results: [
{
id: "10",
phone: "+91783XXXX345",
name: "Mr Example",
email: "freaky#jolly.com"
},
{
id: "11",
phone: "+9178XXXX66",
name: "Mr Foo",
email: "freaky#jolly.com"
}],
statusCode: "1",
count: "2"
}
I was facing this issue
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.hsa.ffgp.hapdfgdfgoon, PID: 25879
java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to
com.hsa.......
Then I mapped data using LinkedTreeMap Key Value as below
...
...
#Override
public View getView(final int i, View view, ViewGroup viewGroup) {
if(view==null)
{
view= LayoutInflater.from(c).inflate(R.layout.listview_manage_clients,viewGroup,false);
}
TextView mUserName = (TextView) view.findViewById(R.id.userName);
TextView mUserPhone = (TextView) view.findViewById(R.id.userPhone);
Object getrow = this.users.get(i);
LinkedTreeMap<Object,Object> t = (LinkedTreeMap) getrow;
String name = t.get("name").toString();
mUserName.setText("Name is "+name);
mUserPhone.setText("Phone is "+phone);
return view;
}
...
...

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
String json = new Gson().toJson("Your Value");
ArrayList<YourClassName> outputList = new Gson().fromJson("Receive Value", new TypeToken<ArrayList<YourClassName>>() {
}.getType());
Log.d("TAG", outputList.get(0).getName);

In my case error occurred while fetching a list of objects from shared preferences.
The error was solved by adding TypeToken as shown below:
public static <GenericClass> GenericClass getListOfObjectsFromSharedPref(Context context,String preferenceFileName, String preferenceKey ){
SharedPreferences sharedPreferences =
context.getSharedPreferences(preferenceFileName, 0);
Type type = new TypeToken<ArrayList<Classname.class>>() {}.getType();
String json = sharedPreferences.getString(preferenceKey, "");
final Gson gson = new Gson();
return gson.fromJson(json, type);
}
....

Related

General Gson Serialization of API types

I am working with an API that can have different types for it's attributes
The attributes can either be Ids or Objects
I want to build a generalized type that handles this for me with Gson serialization.
Example:
"platforms": [
6
]
"platforms": [
{
"id": 6,
"name": "PC (Microsoft Windows)",
"slug": "win",
"url": "https://www.igdb.com/platforms/win",
"created_at": 1297639288000,
"updated_at": 1470063140518,
"website": "http://windows.microsoft.com/",
"alternative_name": "mswin"
}
]
I am working with Kotlin and have started building my Generalizable class
data class ObjectType<T>(
var Id: Long? = null,
var expand: T? = null
)
I am currently stuck in constructing my JsonDeserializer, as it needs a return of type T which in my case can be both an Int or an Object. I have tried to replace the T with ObjectType which works 'better' but cannot handle the cases when the JSON is an array.
I am currently trying to make it work with just the Generalized Type T as I can set the type as List> instead.
Current Implementation:
class ObjectDeserializer<T> : JsonDeserializer<T> {
override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): T {
if (json != null) {
if (json.isJsonArray) {
val struct: T = Gson().fromJson(json, T::class.java) as T
return struct
} else {
val id = Gson().fromJson(json, Long::class.java)
//return ObjectType(id, null)
}
}
return T as T
}
}
I would love some input on how to solve this.
Your implementation has some issues and inconsistencies. First you have to make sure to deserialize ObjectType<T>. Thus you have to declare the class as:
class ObjectDeserializer<T> : JsonDeserializer<ObjectType<T>>
It would also be easier to assume that all parameters are non-null:
override fun deserialize(json: JsonElement, typeOfT: Type,
context: JsonDeserializationContext): ObjectType<T>
Now you can use typeOfT which is actually the type of T in JsonDeserializer, not in ObjectDeserializer. Therefore it's the type of ObjectType<T> you need to deserialize. To move to the next step you need to find the actual type of T:
val objectTypeType = typeOfT as ParameterizedType
val actualTypeOfT = objectTypeType.getActualTypeArguments()[0]
As the next step you need to figure out the contents of json. In your case you won't ever find an array, but an object or a long:
return if (json.isJsonObject()) {
val struct: T = context.deserialize(json, actualTypeOfT)
ObjectType(expand = struct)
} else {
val id = json.getAsLong()
ObjectType(Id = id)
}
Here you return the ObjectType instances without any error handling, which you might need to add as well.
Then you should provide this deserializer to Gson by:
registerTypeAdapter(ObjectType::class.java, ObjectDeserializer<Any>())
Whenever Gson needs to deserialize an ObjectType<TheType>, it finds the instance of ObjectDeserializer and provides ObjectType<TheType> as typeOfT to deserialize.

Getting Gson TypeToken from class name as a String

I want to do something like this - deserialize a class with generic type using Gson. The following code works like a charm.
Type type = new TypeToken<ActionTask<UptimeAction>>() {}.getType();
ActionTask task = (ActionTask) gson.fromJson(json, type);
But what if the type is provided as a String? I imagine something like the following.
String className = "UptimeAction";
Type type = ... // get the type somehow
ActionTask task = (ActionTask) gson.fromJson(json, type);
Is this possible at all?
Based on the Guava docs, you can create a method like this:
static <T> Type mapActionTask(Class<T> innerType) {
return new TypeToken<ActionTask<T>>() {}
.where(new TypeParameter<T>() {}, innerType)
.getType();
}
And call it like this:
String className = "com.foo.UptimeAction";
Type type = mapActionTask(Class.forName(className));

Convert Java List to Javascript Array

I have the following java code in my Android application and wanted a way to convert the Java list to an array that can be used in javascript:
Java:
public void onCompleted(List<GraphUser> users, Response response) {
for(int i = 0; i < users.size(); i++)
{
//add to an array object that can be used in Javascript
webView.loadUrl("javascript:fetchFriends(arrObj)");
}
}
Javascript:
//this is how I want to be able to use the object in Javascript
function parseFriends(usersObjectFromJava){
var users = [];
for (var i = 0; i < usersObjectFromJava.length; i++) {
var u = {
Id: usersObjectFromJava[i].id + "",
UserName: usersObjectFromJava[i].username,
FirstName: usersObjectFromJava[i].first_name,
LastName: usersObjectFromJava[i].last_name,
};
users[i] = u;
}
}
Could some help me with the Java code to create the usersObjectFromJava so that it can be used in javascript?
Use GSON
to convert java objects to JSON string, you can do it by
Gson gson = new Gson();
TestObject o1 = new TestObject("value1", 1);
TestObject o2 = new TestObject("value2", 2);
TestObject o3 = new TestObject("value3", 3);
List<TestObject> list = new ArrayList<TestObject>();
list.add(o1);
list.add(o2);
list.add(o3);
gson.toJson(list) will give you
[{"prop1":"value1","prop2":2},{"prop1":"value2","prop2":2},{"prop1":"value3","prop2":3}]
Now you can use JSON.parse(), to deserialize from JSON to Javascript Object.
I would assume doing this:
Java:
public void onCompleted(List<GraphUser> users, Response response) {
JSONArray arr = new JSONArray();
JSONObject tmp;
try {
for(int i = 0; i < users.size(); i++) {
tmp = new JSONObject();
tmp.put("Id",users.get(i).id); //some public getters inside GraphUser?
tmp.put("Username",users.get(i).username);
tmp.put("FirstName",users.get(i).first_name);
tmp.put("LastName",users.get(i).last_name);
arr.add(tmp);
}
webView.loadUrl("javascript:fetchFriends("+arr.toString()+")");
} catch(JSONException e){
//error handling
}
}
JavaScript:
function fetchFriends(usersObjectFromJava){
var users = usersObjectFromJava;
}
You will have to change the Java-Code a bit (i.e. using public getters or add more/less information to the JSONObjects.
JSON is included in Android by default, so no external libraries are necessary.
I hope i understood your problem.
Small thing i came across: you where using fetchFriends in Java but its called parseFriends in Javascript, I renamed them to fetchFriends
You can use Gson Library.
Gson gson = new GsonBuilder().create();
JsonArray jsonArray = gson.toJsonTree(your_list, TypeClass.class).getAsJsonArray();
http://google-gson.googlecode.com/svn/trunk/gson/docs/javadocs/com/google/gson/Gson.html
Use Jackson.
You'll need to add an " #JsonProperty" annotation to every property of your POJOs you want to pass, then do something like this:
String respStr = "";
for(Object whatever: MyList)
{
JSONObject dato = new JSONObject();
dato.put("FirstField", whatever.SomeData());
dato.put("SecondField", whatever.SomeData2());
StringEntity entity = new StringEntity(dato.toString());
post.setEntity(entity);
webView.loadUrl("javascript:fetchFriends("+entity+")");
}
I am not sure why no answer mentioned about jaxb. I am just thinking jaxb would be a good fit for this type of problems...
For a sample style of annotated jaxb class, please find this.
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class ResponseAsList {
private List < Object > list = new ArrayList < Object > ();
public ResponseAsList() {
// private default constructor for JAXB
}
public List < Object > getList() {
return list;
}
public void setList(List < Object > list) {
this.list = list;
}
}
You will stuff your data in these lists and you will marshal either in xml or a json. After you get a json to the client, you can do a var myArray = JSON.parse(response);...
Although I typically advocate using something like GSON or Jackson to do JSON conversions for you, its pretty easy to roll your own if you're in a limited environment (like Android) and don't want to bundle a bunch of dependencies.
public class JsonHelper {
public static String convertToJSON(List<GraphUser> users) {
StringBuilder sb = new StringBuilder();
for (GraphUser user : users) {
sb.append(convertToJSON(user));
}
return sb.toString();
}
public static String convertToJSON(GraphUser user) {
return new StringBuilder()
.append("{")
.append("\"id\":").append(user.getId()).append(",")
.append("\"admin\":").append(user.isAdmin() ? "true" : "false").append(",")
.append("\"name\":\"").append(user.getName()).append("\",")
.append("\"email\":\"").append(user.getEmail()).append("\"")
.append("}")
.toString();
}
}
You could obviously make a toJSON() method on GraphUser to put the logic if you prefer. Or use an injectable json helper library instead of static methods (I would). Or any number of other abstractions. Many developers prefer to separate representation of model objects into their own object, myself included. Personally, I might model it something like this if I wanted to avoid dependencies:
interface Marshaller<F,T> with methods T marshall(F obj) and F unmarshall(T obj)
interface JsonMarshaller<F> extends Marshaller<String>
class GraphUserMarshaller implements JsonMarshaller<GraphUser>
class GraphUserCollectionMarshaller implements JsonMarshaller<Collection<GraphUser>> which could do type-checking or use the visitor pattern or something to determine the best way to represent this type of collection of objects.
Along the way, I'm sure you'll find some repeated code to extract to super- or composite- classes, particularly once you start modeling collection marshallers this way. Although this can get to be pretty verbose (and tedious), it works particularly well in resource-constrained environments where you want to limit the number of libraries on which you depend.
You can use the Google Gson library (http://code.google.com/p/google-gson/) to convert the Java List Object to JSON. Ensure that the right fields are set like ID, UserName, FirstName, etc and on the java script side that same code would work.
Its just an example, First add javascript interface. It will be a bridge between javascript and java code.
webView.addJavascriptInterface(new JSInterface(), "interface");
In javascript you can add like this,
"window.interface.setPageCount(pageCount1);"
The interface is a keyword in common between java and javascript. create a class JSInterace and define a method setPageCount(int a). The script will return a value, and you can use that value in your java method

Map JSONArray to object by position

In an API I'm working with I receive a certain part as a plain JSON array instead of a normal key => value pair. Its stupid, but that's what I have to deal with.
Example:
{"build":29625,"list": [
[312218505,1000,624437010,21997878697,35228,0,0,0],
[186873474,0,0,0,0,-1,0,0]
...
]}
The problem is getting Google GSON to map each array position to a field in a normal Object. Since unsurprisingly there isn't any native support for this I've had to roll my own solution: Each "request" that needs conversion implements a CustomConvert interface which has convertToObjects() and convertToJson() methods. Each "entry" has a bunch of fields with a #ArrayPosition(number) annotation. The convert methods translate between the two lists.
As this is ugly, is there a better way to do this with Google GSON or even another JSON library for Java?
Example from comments above, but this is the line of thinking I'm trying to get at. Note I haven't tested this or anything but at the very least it should put you on the path if you wanted to go this route.
class OuterObject {
String build;
List<InnerObject> objectList = new ArrayList<InnerObject>();
}
class InnerObject {
int field1, field2, field3, field4, field5, field6, field7, field8;
public InnerObject(int[] params) {
// assign params accordingly
}
}
class MyDeserializer implements JsonDeserializer<OuterObject> {
#Override
public OuterObject deserialize(JsonElement je, Type type, JsonDeserializationContext jdc) throws JsonParseException
{
OuterObject oo = new OuterObject();
JsonObject jo = je.getAsJsonObject();
oo.build = jo.get("build").getAsString();
JsonArray innerObjArrays = jo.getAsJsonArray("list");
for (JsonElement e : innerObjArrays)
{
JsonArray innerArray = e.getAsJsonArray();
// This is ugly but should give you the gist
InnerClass ic = new InnerClass((int[])jdc.deserialize(innerArray, int[].class));
oo.objectList.add(ic);
}
return oo;
}
}

Exclusion Strategy in gson

My JSON has the following structure:
{"name": [9000, {Inst1}, ..., {Instn}]}
Where 9000 is an arbitrary integer and Insti are serialized instances of some class.
I'm using something like this for getting all the Inst into the list:
Type listType = new TypeToken<ArrayList<Song>>(){}.getType();
and trying go exclude that first int by writing something like this:
public class ExcludeTotalFound implements ExclusionStrategy {
private final Class<?> typeToSkip;
public ExcludeTotalFound(Class<?> typeToSkip) {
this.typeToSkip = typeToSkip;
}
public boolean shouldSkipClass(Class<?> clas_s) {
return clas_s == typeToSkip;
}
public boolean shouldSkipField(FieldAttributes fieldAttributes) {
return typeToSkip.equals(fieldAttributes.getDeclaredClass());
}
}
And, finally, I'm doing
gson = new GsonBuilder().addDeserializationExclusionStrategy(new ExcludeTotalFound(int.class)).serializeNulls().create();
and, then:
collection = gson.fromJson(rBody, listType);
where rBody is all that raw array, i.e. {"name": [9000, {Inst1}, ..., {Instn}]
But all what I get is
com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was NUMBER`
What's the problem?
ADD:
As long, as I know that the length of my JSON will never exceed ~500, and that the structure remains always the same, is it good enough to use the following workaround?
Iterator<JsonElement> it = rBody.iterator();
it.next();
while (it.hasNext()) {
collection.add(gson.fromJson(it.next(), Song.class));
}
This looks pretty similar to this one I answered over here -->
Gson custom deserialization. Does that help? It's not by exclusion, but rather by custom deserialization.

Categories

Resources