Gson.toJson 返回 null [ProGuard 问题]
Posted
技术标签:
【中文标题】Gson.toJson 返回 null [ProGuard 问题]【英文标题】:Gson.toJson returns null [ProGuard issue] 【发布时间】:2020-03-02 08:30:05 【问题描述】:用户错误报告显示Gson().toJson(obj)
偶尔会返回,但对于大多数用户来说它工作正常。
我拜访了一位用户,他在手机上遇到了错误并调试了应用程序,我制作了Toast
来显示正在发送到服务器的内容,我看到了 Toast 显示 以及 记录 和ID 不是
null
。
这就是我所做的。
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;
我在这里序列化对象
ClassA obj = new ClassA(ID,Records);
String json = new Gson().toJson(obj);
但new Gson().toJson(obj
) 对于某些用户可以正常工作,但对于某些用户返回
服务器数据库显示一些用户发送了数据,但一些正确的 json。经过一些研究我发现
new Gson().toJson(obj
) 返回。没有网络服务问题,也没有数据库问题。
额外信息
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);
数据库
id | data
----------------
c89 | "ID":"c89","Records":["T":0,"F":0,"D":"2019/04/11 05:48 PM"] correct one
c90 | bug
空输入测试
我做了下面的测试,我发现问题超出了空输入
ArrayList<ClassB> Records= new ArrayList<>();
ClassA obj = new ClassA(null,Records);
Toast.makeText(getApplicationContext(),new Gson().toJson(obj ),Toast.LENGTH_LONG).show();
Toast 显示 "Records":[]
.worse 比上限条件永远不会发生
IDE 也说
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 ----------
我如何使它对所有用户都正常工作?
评论
这应该是不可能的,您需要有关 案例的更多详细信息,例如 是否一致,是否由于多线程,是否是特定的 JDK 版本
没有任何多线程现象。例如,没有Runnable
没有AsycTask
没有Thread
。它只是一个从内容提供者获取数据并创建json字符串的普通片段。
建议的解决方案具有相同的结果!
ClassA obj = new ClassA(ID,Records);
Gson gson = new GsonBuilder().serializeNulls().create();
Toast.makeText(getActivity(), gson.toJson(obj), Toast.LENGTH_LONG).show();
Toast 再次显示。
【问题讨论】:
尝试将getters
添加到两个类中
这应该是不可能的,您需要有关
案例的更多详细信息,例如是否一致,是否由于多线程,是否为特定的 JDK 版本。
这看起来应该作为错误报告发布到 Gson 项目。但是,他们会要求您提供比这里更多的信息。请注意。
@KarolDowbecki 这不是多线程
您应该遵循 Java 命名约定:变量名称应该用驼峰命名,例如Records
应该是records
,ID
应该是id
和T
,F
和D
应该分别是t
,f
和d
。
【参考方案1】:
这是因为ClassA
中的ID
和Records
为空。 Gson 默认不序列化空值,实际上有一个方法可以启用序列化空值,我几乎总是启用:
private static final Gson GSON = new GsonBuilder().serializeNulls().create();
我建议保留一个 Gson 的静态实例,这样您就不必在每次想要序列化时都创建一个。
如果您不想序列化 null,那么您还有其他一些选项来修复您的代码,例如在 ClassA
的构造函数中进行 null 检查。
【讨论】:
@Mr.AF 您说您正在查看服务器数据库中的 JSON 数据。您能否通过打印语句或调试日志确认任何 JSON 数据是“”?错误可能不在您发布的代码中,而是在上传或查看 json 的方式中。 查看问题的最后一部分 @Mr.AF 啊,我没看到。您能告诉我们 ID 和 Records 的哪些值使它给出“”吗? 查看有问题的数据库记录 @Mr.AF 是的,我看到数据库包含一个“”。能不能在本地调试一下,看看json字符串等于“”时ID和Records的值是什么。可能在 Toast、print 语句或 log 语句中调试所有三个值。【参考方案2】:只是我的猜测,我看到您在其中一条评论中提到 ClassA
和 ClassB
是片段内的内部类。所以,如果你还没有尝试的话,也许你可以尝试一些事情。
第一:把它们改成静态内部类怎么样?
private static class ClassA
...
private static class ClassB
...
将类声明为内部类,因为没有其他人使用它是可以的,我有时也这样做。但是非静态内部类依赖于它的父对象实例,因此当它只是一个简单的 bean/DTO 类时,将它们声明为静态内部类要安全得多。
第二:尝试摆弄这些类的Proguard配置
这里是 a *** question,关于设置 Proguard 以保留 内部类。
查看-keep class <your packge name>.** <fields>;
部分
第三:也许尝试将这些类提取到一个普通类中,以缩小可疑区域。
如果它仍然不起作用,那么问题可能出在其他地方,可能是客户端移动设备的某个特定构建/版本或其他东西。
【讨论】:
听起来不错。您的建议听起来很实用,可能会以某种方式解决问题。测试需要时间。我会测试和反馈。【参考方案3】:ArrayList<ClassB> Records= new ArrayList<>();
ClassA obj = new ClassA(null,Records);
Toast.makeText(getApplicationContext(),new Gson().toJson(obj
),Toast.LENGTH_LONG).show();
在这种情况下,您的 rRecord arraylist 为空而不是 null。试试这个检查
if(!Records.isEmpty())
// show toast
【讨论】:
是的,它是空的。正如我所说的这种情况是可能发生的更糟糕的情况,但正如我所说的 IDE 说更糟糕的情况永远不会发生,尽管我假设会发生【参考方案4】:你可能在某处抓到JsonParseException
吗?当一个类型没有注册到TypeAdapter
时,RuntimeTypeAdapterFactory 有时会引起问题。尝试征用 RuntimeTypeAdapterFactory
类并在您的项目使用的版本中,替换每次出现的
if (delegate == null)
throw new JsonParseException("cannot deserialize " + baseType + " subtype named "
+ label + "; did you forget to register a subtype?");
有
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"
【讨论】:
解决这个问题的难点在于没有任何异常......【参考方案5】:试试这个
GsonBuilder builder = new GsonBuilder().serializeSpecialFloatingPointValues();
String data = builder.create().toJson(obj);
【讨论】:
它有什么帮助?有什么不同?为什么? 当您的 JSON 具有浮动值并尝试在字符串中进行隐藏时会有所帮助 谢谢。但是所有值都是int,没有任何浮点值。值是从数据类型为INT的内容提供者恢复的。以上是关于Gson.toJson 返回 null [ProGuard 问题]的主要内容,如果未能解决你的问题,请参考以下文章
Gson toJson 返回 JSON 字符串而不是 JSON 对象