【问题标题】:Deserializing nested firebase firestore classes on Android在 Android 上反序列化嵌套的 firebase firestore 类
【发布时间】:2021-12-13 05:08:44
【问题描述】:

问题描述

我正在尝试反序列化 Android 应用的简单 Firebase Firestore 文档。
问题在于 kotlin 嵌套类反序列化。它应该是一个非常简单的方法,但它似乎不起作用。

我有一个类希望将远程文档上的地图内容存储为 kotlin 数据类。
尽管@PropertyName-提供的字段名称与关联的类属性名称匹配,并且非嵌套类(称为Bucket)正确接收所有“原始”数据,但它的嵌套类似乎没有被填充使用远程数据,回退到您必须提供的空构造函数值,从而创建一个垃圾对象。

实现细节

FirebaseBucket文档数据结构如下:

我创建了以下类来表示整个文档:

@Parcelize
data class Bucket(
    @PropertyName("id")
    val id: String,

    @PropertyName("name")
    val name: String,
    @PropertyName("description")
    val description: String,

    @PropertyName("bucket_style")
    val style: BucketStyle,

    @PropertyName("bucket_dates")
    val dates: BucketDates
) : Parcelable {
    constructor() : this("mock.id", "mock.name", "mock.desc", BucketStyle(), BucketDates(), listOf())
}

BucketStyle 类为例:

@Parcelize
data class BucketStyle(
    @PropertyName("icon_family")
    val iconFamily: Int,

    @PropertyName("icon_id")
    val iconId: Int,

    @PropertyName("color_id")
    val colorId: Int
) : Parcelable {
    constructor() : this(0, 0, 1)
}

并且对称:

@Parcelize
data class BucketDates(
    @PropertyName("creation_date_timestamp")
    val creationDate: Timestamp,

    @PropertyName("last_edit_timestamp")
    val lastEdit: Timestamp
) : Parcelable {
    constructor() : this(Timestamp.now(), Timestamp.now())
}

我使用默认的 firebase 提供的方法(28.4.2firebase-bom 版本)反序列化它(实际上是使用实时侦听器在集合中的所有文档)

buckets.addSnapshotListener { data, error -> 
    val update: List<Bucket> = data.toObjects(Bucket::class.java)
}

并且没有填充内部类,如此调试器所示,因为服务器值不同,它们使用默认值,而 ID、名称和其他所有“原始”都可以正常工作。

我没有收到错误,只是在执行时出现 logcat 警告:

W/Firestore: (23.0.4) [CustomClassMapper]: No setter/field for bucket_style found on class com.[redacted].model.bucket.Bucket
W/Firestore: (23.0.4) [CustomClassMapper]: No setter/field for bucket_dates found on class com.[redacted].model.bucket.Bucket

补充分析

那些日志让我感到困惑,因为我确实创建了 getter 和 setter,因为 Bucket 是一个数据类。还是我?

我将 kotlin 文件反编译为 java,我意识到我们的对象没有名为 getbucket_style() 的 getter:

@PropertyName("bucket_style")
@NotNull
private final BucketStyle style;

@NotNull
public final BucketStyle getStyle() {
    return this.style;
}

我们只有getStyle()

我们可以尝试通过在Bucket 中将 kotlin style 属性重命名为 bucket_style(以匹配 firestore 文档字段名称)来做一个技巧:

@PropertyName("bucket_style")
val bucket_style: BucketStyle,

java反编译成:

@PropertyName("bucket_style")
@NotNull
private final BucketStyle bucket_style;

@NotNull
public final BucketStyle getBucket_style() {
    return this.bucket_style;
}

但警告保持不变。生成的 getter 有一个大写的“B”(这是在 kotlin 数据类中生成 getter 的语法约定)。
但是,如果我在原始 Bucket 中创建一个名为 getbucket_style() 的自定义 getter,如下所示:

@Parcelize
data class Bucket(
    @PropertyName("id")
    val id: String,

    @PropertyName("name")
    val name: String,
    @PropertyName("description")
    val description: String,

    @PropertyName("bucket_style")
    val style: BucketStyle,

    @PropertyName("bucket_dates")
    val dates: BucketDates
) : Parcelable {

    fun getbucket_style() = style

    constructor() : this("mocker.id", "mocker.name", "mocker.desc", BucketStyle(), BucketDates(), listOf())
}

谁的java反编译长这样:

@PropertyName("bucket_style")
@NotNull
private final BucketStyle style;


@NotNull
public final BucketStyle getbucket_style() {
    return this.style;
}

@NotNull
public final BucketStyle getStyle() {
    return this.style;
}

然后警告改变了!

W/Firestore: (23.0.4) [CustomClassMapper]: No setter/field for style found on class com.[redacted].model.bucket.Bucket (fields/setters are case sensitive!)

我不知道为什么。另请注意,在第一个警告中,该属性称为bucket_style,此处称为style

如果我像以前一样做同样的把戏,将 style 重命名为 bucket_style,我们会得到:

@Parcelize
data class Bucket(
    @PropertyName("id")
    val id: String,

    @PropertyName("name")
    val name: String,
    @PropertyName("description")
    val description: String,

    @PropertyName("bucket_style")
    val bucket_style: BucketStyle,

    @PropertyName("bucket_dates")
    val dates: BucketDates
) : Parcelable {

    fun getbucket_style() = bucket_style

    constructor() : this("mock.id", "mock.name", "mock.desc", BucketStyle(), BucketDates(), listOf())
}

java反编译的样子

@NotNull
private final String description;

@PropertyName("bucket_style")
@NotNull
public final BucketStyle getbucket_style() {
    return this.bucket_style;
}

@NotNull
public final BucketStyle getBucket_style() {
    return this.bucket_style;
}

这会在没有警告的情况下崩溃,因为(我相信)有两个函数具有相同的、不区分大小写的名称:

E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.[redacted], PID: 18996
    java.lang.RuntimeException: Found conflicting getters for name getbucket_style on class com.[redacted].model.bucket.Bucket

结论

另一种方法是使用 kotlin Maps 对嵌套的 firestore 地图进行建模,但类更易于使用。
我也尝试取消包裹所有课程,但问题仍然存在。
是否有干净的解决方案/实施有问题?

编辑

解决方法是更改​​类属性名称以匹配 Firestore 文档字段名称。

我的问题是一个更普遍的错误的特例。反序列化对子类不起作用是不正确的。它不适用于名称与 firebase 不匹配的 任何 类字段(在我的情况下)。

确实,我所要做的就是将地图名称从“bucket_style”更改为“style”,并将内部字段(在现在的“style”地图中)更改为使用驼峰式大小写(以匹配我的应用程序声明)和瞧,数据现在去往它应该去的地方。现在我在后端和模型上使用相同的名称,这样反序列化器就不会混淆。

不过,我不知道这怎么行。我相信这是我正在触发的事情(我拒绝相信到目前为止没有人遇到此问题并报告它),即使我的应用程序仍然很小,请遵循 de facto 准则并专门使用 firebase火库。

【问题讨论】:

  • 您尝试过使用地图吗?是这样的吗?
  • 嘿!我会试试的,我也可以尝试用另一个库解码 JSON(我在想 gson)。这些可能会起作用,但是恕我直言,它们不是很干净的解决方案,我想了解是否(以及如何)使用映射到类的反序列化功能。
  • 这也应该适用于类,但您所要做的就是在反序列化时注意您使用的名称。
  • 就是这样,我认为我做的一切都是正确的,但它似乎并没有按预期工作。

标签: android firebase kotlin serialization google-cloud-firestore


【解决方案1】:

我怀疑这是 Firestore 的 @PropertyName 注释和 Kotlin 的数据类的错误,您必须找到解决方法。

首先要注意的是 Kotlin 数据类没有设置器。在其构造函数中初始化属性的值不能更改。许多 JSON 反序列化器习惯于“Java 方式”做事:寻找无参数的构造函数,调用它,然后寻找设置器来设置 JSON 文档中的每个字段。错误 No setter/field for bucket_style found on class com.[redacted].model.bucket.Bucket 告诉我们 Firestore 正在调用无参数构造函数(使用您的默认值),然后寻找设置器。

奇怪的是,Firestore 正确地在父对象 Bucket 上设置字段,它也有一个无参数构造函数。

一些值得研究的事情:

  • 如果 BucketStyle 对象直接存储在 Firebase 中(例如,如果您添加 id 并将其非嵌套存储在 Firestore 中),Firestore 是否正确反序列化它?

  • 如果您删除BucketStyle 上的无参数构造函数(使用默认值),问题是否会解决?这可能会强制 Firestore 反序列化程序使用带有命名参数的构造函数。

  • 如果从类声明中删除 data 并将 val 字段更改为 var 字段,问题是否会解决?在后台,这应该为每个字段创建设置器。

【讨论】:

  • 真的很有见地。我会尽快尝试你所有的想法。如果不起作用,我将提交错误报告并使用地图反序列化嵌套地图。
  • 好吧,这很奇怪。类本身的反序列化不起作用。更改了 firebase 字段名称以匹配 kotlin 数据类中的实际名称,并且可以正常工作。所以基本上@PropertyName 是没用的。
  • 现在,无论该类是普通类还是数据类(都带有var成员)和@PropertyName/类字段名称不同,问题依然存在。
  • 如果我删除无参数构造函数,代码将无法编译,因为 firebase 反序列化方法需要将一个附加到您的类。
  • 是的。类字段名称需要与 firebase 名称相同。 @ PropertyName 出于某种原因被忽略了,至少对于我的项目而言。
猜你喜欢
  • 1970-01-01
  • 2018-05-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-08-01
  • 2021-12-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多