users Bug report shows Gson().toJson(obj)
occasionally returns {}
but for most users it works correct.
i have visited an user who faced the bug and debugged app on his phone and i made Toast
to show what is sending to server and i saw Toast shows {}
and also Records and ID aren't null
.
here is what i have done.
private class ClassA{
String ID;
ArrayList<ClassB> Records;
ClassA(String ID, ArrayList<ClassB> Records) {
this.ID= ID;
this.Records= Records;
}
}
private class ClassB {
int T;
int F;
String D;
ClassB (int T, int F, String D) {
this.T= T;
this.F = F;
this.D= D;
}
}
And here i do serialize object
ClassA obj = new ClassA(ID,Records);
String json = new Gson().toJson(obj);
but new Gson().toJson(obj
) for some users works correct but for some return {}
Server database shows some users sent data {}
but some correct json.after some research i found new Gson().toJson(obj
) returns {}
.no webservice problem and no database problem.
Extra info
ArrayList<ClassB> Records= new ArrayList<>();
Records.add(new ClassB(Integer.valueOf(t.toString()), Integer.valueOf(f.toString()),d.toString()));
ClassA obj = new ClassA(ID,Records);
String json = new Gson().toJson(obj);
Database
id | data
----------------
c89 | {"ID":"c89","Records":[{"T":0,"F":0,"D":"2019/04/11 05:48 PM"}]} correct one
c90 | {} bug
Null Input Test
i did below test and i found problem is beyond the null inputs
ArrayList<ClassB> Records= new ArrayList<>();
ClassA obj = new ClassA(null,Records);
Toast.makeText(getApplicationContext(),new Gson().toJson(obj ),Toast.LENGTH_LONG).show();
Toast shows {"Records":[]}
.worse than upper condition never happens
And also IDE says
if(ID!=null && Records!=null) <= this condition is always true
ClassA obj = new ClassA(ID,Records);
proguard-rules.pro
##---------------Begin: proguard configuration for Gson ----------
# Gson uses generic type information stored in a class file when working with fields. Proguard
# removes such information by default, so configure it to keep all of it.
-keepattributes Signature
# For using GSON @Expose annotation
-keepattributes *Annotation*
# Gson specific classes
-dontwarn sun.misc.**
#-keep class com.google.gson.stream.** { *; }
# Application classes that will be serialized/deserialized over Gson
-keep class com.google.gson.examples.android.model.** { <fields>; }
# Prevent proguard from stripping interface information from TypeAdapter, TypeAdapterFactory,
# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)
-keep class * implements com.google.gson.TypeAdapter
-keep class * implements com.google.gson.TypeAdapterFactory
-keep class * implements com.google.gson.JsonSerializer
-keep class * implements com.google.gson.JsonDeserializer
# Prevent R8 from leaving Data object members always null
-keepclassmembers,allowobfuscation class * {
@com.google.gson.annotations.SerializedName <fields>;
}
##---------------End: proguard configuration for Gson ----------
How do i make it to work correct for all users?
Comments
This shouldn't be possible, you need more details on the {} case eg is it consistent, is it due to multi-threading, is it a specific JDK version
There isn't any multi-threading phenomena.for example, no Runnable
no AsycTask
no Thread
.it's just a normal fragment which gets data from content provider and create json string.
Suggested solution has same result !
ClassA obj = new ClassA(ID,Records);
Gson gson = new GsonBuilder().serializeNulls().create();
Toast.makeText(getActivity(), gson.toJson(obj), Toast.LENGTH_LONG).show();
Toast shows {}
again.
This is because ID
and Records
in ClassA
are null. Gson does not serialize nulls by default and actually has a method to enable serializing nulls, which I almost always enable:
private static final Gson GSON = new GsonBuilder().serializeNulls().create();
I recommend keeping a static instance of a Gson around so you don't have to keep creating one each time you want to serialize.
If you don't want to serialize nulls, then you have some other options to fix your code, such as having null checks in the constructor of ClassA
.
Just my wild guess, I saw you mentioned in one of the comment that ClassA
and ClassB
are inner classes inside a fragment. So, maybe there's a few thing you could try, if haven't already.
First: How about changing them into static inner class?
private static class ClassA {
...
}
private static class ClassB {
...
}
Having classes declared as inner class because no one else use it is okay, I did that sometimes too. But non-static inner class kind of depended on its parent object instance, so declaring them static inner class is much safer, when it's just a simple bean/DTO classes.
Second: Try fiddling around Proguard configuration about those classes
Here's a stackoverflow question about setting up Proguard to keep inner classes .
Review the -keep class <your packge name>.** { <fields>; }
-keep class <your packge name>.** { <fields>; }
part
Third: Maybe try extracting those classes into a normal class just to narrow down the suspect area.
If it still doesn't work, then the problem probably lies somewhere else, maybe some specific build/version of the client mobile device or something.
ArrayList<ClassB> Records= new ArrayList<>();
ClassA obj = new ClassA(null,Records);
Toast.makeText(getApplicationContext(),new Gson().toJson(obj
),Toast.LENGTH_LONG).show();
In this case you rRecord arraylist is empty not null. try this checking
if(!Records.isEmpty()){
// show toast
}
In your code, there are some interesting things to point out:
For your problem: - Gson does not convert null members by default. - Gson does not convert transient members by default.
Use GsonBuilder to customize a Gson instance for your case, for your need.
Thanks @Montri M for nice suggestions:
Second: Try fiddling around Proguard configuration about those classes
Here's a stackoverflow question about setting up Proguard to keep inner classes.
Review the -keep class.** {; } part
Third: Maybe try extracting those classes into a normal class just to narrow down the suspect area.
i created DataModel
and extracted inner classes and insert them into DataModel
public class DataModel {
public static class ClassA{
String ID;
ArrayList<ClassB> Records;
ClassA(String ID, ArrayList<ClassB> Records) {
this.ID= ID;
this.Records= Records;
}
}
public static class ClassB {
int T;
int F;
String D;
ClassB (int T, int F, String D) {
this.T= T;
this.F = F;
this.D= D;
}
}
}
And i put these two lines into Proguard-rules.pro
.Please do Attention to $
sign
-keep class <package-name>.DataModel.** { *; }
-keep class <package-name>.DataModel$** { *; }
Now i see problem resolved and Gson serializes obj correct.
Are you possibly catching a JsonParseException
somewhere? RuntimeTypeAdapterFactory sometimes causes trouble when a type is not registered with the TypeAdapter
. Try commandeering the RuntimeTypeAdapterFactory
class and in the version that your project uses, replace every occurrence of
if (delegate == null) {
throw new JsonParseException("cannot deserialize " + baseType + " subtype named "
+ label + "; did you forget to register a subtype?");
}
With
if (delegate == null) {
/*
This class is commandeered from Google/Gson. The edit below was added so that if a type
is not registered with the TypeAdapter, the app still works instead of throwing an
exception and crash.
*/
delegate = (TypeAdapter<R>) labelToDelegate.get(Constants.DEFAULT_TYPE); // "DefaultType"
}
Try this
GsonBuilder builder = new GsonBuilder().serializeSpecialFloatingPointValues();
String data = builder.create().toJson(obj);
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.