如何使用getValue(Subclass.class)在Firebase中反序列化一个子类

14
我正在为 Android 使用新的 Firebase SDK 并使用真实数据库功能。当我使用 getValue(simple.class) 时一切正常。但当我想解析一个是子类的类时,母类的所有属性都是 null,并且我会有这种类型的错误:

No setter/field for name found on class uk.edume.edumeapp.TestChild

public class TestChild  extends TestMother {

    private String childAttribute;

    public String getChildAttribute() {
        return childAttribute;
    }
}

public class TestMother {

    protected String motherAttribute;

    protected String getMotherAttribute() {
        return motherAttribute;
    }
}
这个函数
snapshot.getValue(TestChild.class);

motherAttribute 属性为 null,并且我得到了:

在类 uk.edume.edumeapp.TestChild 中未找到 motherAttribute 的 setter/field。

我解析的 Json 如下:

{
  "childAttribute" : "attribute in child class",
  "motherAttribute" : "attribute in mother class"
}

似乎是 Firebase 的一个 bug。我在我的端口也复制了你的问题。不知道如何解决它。 - Talha Mir
4个回答

18

Firebaser在此

这是Firebase Database SDK for Android某些版本已知的bug:我们的序列化/反序列化仅考虑声明类上的属性/字段。

在Firebase Database SDK for Android 9.0至9.6(如我所记)的发布版本中,缺少从基类继承的属性的序列化。自那时以来的版本中已添加回来。

解决方法

同时,您可以使用Jackson(Firebase 2.x SDK在后台使用的工具)使继承模型正常工作。

更新:以下是一个示例,说明如何将JSON数据读取到您的TestChild中:

public class TestParent {
    protected String parentAttribute;

    public String getParentAttribute() {
        return parentAttribute;
    }
}
public class TestChild  extends TestParent {
    private String childAttribute;

    public String getChildAttribute() {
        return childAttribute;
    }
}

你会注意到我将getParentAttribute()方法更改为public,因为只有公共属性/获取器才会被考虑。通过这个更改,这个JSON变成了:

{
  "childAttribute" : "child",
  "parentAttribute" : "parent"
}

通过以下方式变得易读:

ObjectMapper mapper = new ObjectMapper();
GenericTypeIndicator<Map<String,Object>> indicator = new GenericTypeIndicator<Map<String, Object>>() {};
TestChild value = mapper.convertValue(dataSnapshot.getValue(indicator), TestChild.class);

GenericTypeIndicator有点奇怪,但幸运的是它是一个可以复制/粘贴的魔法咒语。


我尝试使用Jackson使其工作:@JsonIgnoreProperties(ignoreUnknown = true) @JsonTypeInfo(use = JsonTypeInfo.Id.NAME,include = JsonTypeInfo.As.PROPERTY) @JsonSubTypes({ @JsonSubTypes.Type(value = TestChild.class,name = "child") }),但它并没有真正帮助我。你知道我应该怎么做吗? - drevlav
1
不需要任何注释。只需使用 new ObjectMapper().convertValue(pojoObject, Map.class),然后将生成的映射传递到 setValue() 中。 - Frank van Puffelen
1
您在这里的设置(使用公共访问器)对我的项目没有任何影响。当进行保存时,我仍然缺少所有父级数据。 - Chad Bingham
1
大部分答案文档都使用Jackson提供了解决方法;我刚刚更清晰地标记了该部分。我当时测试的确切代码是有效的。如果您在应用相同的解决方法时遇到麻烦,建议您使用最小化、完整的代码来重现问题并提出问题。 - Frank van Puffelen
2
@FrankvanPuffelen 我猜应该有一种方法可以反向实现,即使用父/子类类型在Firebase中设置一个值?(例如:reference.setValue(testChild);) 感谢您提供的解决方法,但是对于那些依赖扩展的许多复杂对象的用户来说,这似乎是一个很大的打击。 是否有任何更新或替代方案?我认为手动创建子类将是更好的选择,而不是为每个调用都进行对象映射,尽管这样做会让我感到不舒服。 - Alan
显示剩余9条评论

6

这个问题在 9.6版本中得到了修复。

修复了一个问题,即将派生类传递给 DatabaseReference#setValue() 方法时,未能正确保存超类的属性。


2

关于:

在uk.edume.edumeapp.TestChild类中找不到motherAttribute的setter/field

为TestChild类添加setter方法:

 public class  TestMother {

     private String motherAttribute;

     public String getMotherAttribute() {
         return motherAttribute;
     }

     //set
     public void setMotherAttribute(String motherAttribute) {
         this.motherAttribute= motherAttribute;
     }
 }

子属性没问题,在反序列化期间它被设置为正确的值,问题在于母属性为空。由于它是在母类中定义的,所以没有被考虑进去。 - drevlav
问题不在于Setter,而是它看不到母类中定义的字段。 - drevlav
我终于注意到你这里的不同之处:你把 getMotherAttribute() 做成了公共的。这就是关键的不同之处。干得好:点赞。 - Frank van Puffelen
1
将其从protected更改为public并没有帮助,子类已经可以访问所有“protected”函数,所以这种更改是无关紧要的。此外,作者提供的答案是在TestChild中放置一个setter并展示在TestMother中放置setter的代码,这不太一致。无论如何,在子类中为母类属性放置setter是没有意义的,而且会破坏多态模式... - drevlav
在子类中编写一个setter/getter方法,它只调用相关的super()方法。例如:protected String getMotherAttribute() { return super.getMotherAttribute(); }。这对我很有效。当他们修复了这个bug时,你可以直接删除这些代码... - Alaa M.

0

请查看https://firebase.google.com/support/guides/firebase-android

它说:

"如果您的JSON中有一个额外的属性,而在您的Java类中没有该属性,则会在日志文件中看到此警告:W/ClassMapper:在com.firebase.migrationguide.ChatMessage类上找不到ignoreThisProperty的setter/field"

您可以在类上放置@IgnoreExtraProperties注释来消除此警告。如果您希望Firebase数据库像2.x SDK一样运行,并在存在未知属性时抛出异常,则可以在类上放置@ThrowOnExtraProperties注释。

Blockquote


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接