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应该是recordsID应该是idTFD应该分别是tfd 【参考方案1】:

这是因为ClassA 中的IDRecords 为空。 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】:

只是我的猜测,我看到您在其中一条评论中提到 ClassAClassB 是片段内的内部类。所以,如果你还没有尝试的话,也许你可以尝试一些事情。

第一:把它们改成静态内部类怎么样?

private static class ClassA 
    ...


private static class ClassB 
    ...

将类声明为内部类,因为没有其他人使用它是可以的,我有时也这样做。但是非静态内部类依赖于它的父对象实例,因此当它只是一个简单的 bean/DTO 类时,将它们声明为静态内部类要安全得多。

第二:尝试摆弄这些类的Proguard配置

这里是 a *** question,关于设置 Proguard 以保留 内部类

查看-keep class &lt;your packge name&gt;.** &lt;fields&gt;; 部分

第三:也许尝试将这些类提取到一个普通类中,以缩小可疑区域。

如果它仍然不起作用,那么问题可能出在其他地方,可能是客户端移动设备的某个特定构建/版本或其他东西。

【讨论】:

听起来不错。您的建议听起来很实用,可能会以某种方式解决问题。测试需要时间。我会测试和反馈。【参考方案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 对象

Gson 忽略值 = null 的地图条目

gson.toJson() 抛出 ***Error

Gson toJson(),奇怪的行为(产生空的 json)

Konlin中的Gson toJson和ArrayList

AJAX,JSON,GSON