问题
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 e.g. 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.
回答1:
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>; }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.
回答2:
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.
回答3:
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
}
回答4:
In your code, there are some interesting things to point out:
- Why do you have private classes? Is it needed there?
- Normally, I would always use List (one of the interfaces of ArrayList) as a type to store a list in it. ArrayList would be the specific type of the object passed there in.
- Variable names always small with camel case.
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.
回答5:
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.
回答6:
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"
}
回答7:
Try this
GsonBuilder builder = new GsonBuilder().serializeSpecialFloatingPointValues();
String data = builder.create().toJson(obj);
来源:https://stackoverflow.com/questions/58720274/gson-tojson-returns-null-proguard-issue