使用 Moshi 反序列化可以是两种数据类型之一的字段

Posted

技术标签:

【中文标题】使用 Moshi 反序列化可以是两种数据类型之一的字段【英文标题】:Deserialize a field that can be one of two data types using Moshi 【发布时间】:2018-02-15 03:48:58 【问题描述】:

我从 OrientDB 服务器接收一些 JSON,看起来像这样:


    ...
    "out": ...,
    "in": ...,
    ...

现在outin 这两个字段可以是以下两种类型之一:String 和我自己的自定义对象(我们称之为Record)。例如,对于一个请求,我可能会收到:


    ...
    "out": "#17:0",
    "in": 
        ...
    ,
    ...

另一个我可能会得到:


    ...
    "out": 
        ...
    ,
    "in": "#18:2",
    ...

等等。两者都可能是Strings,两者都可能是Records,一个可能是String,另一个可能是Record,等等。现在,当我使用Moshi 反序列化这种JSON 时,我将有两个参数outin 来保存它们各自键的值;但是,由于这些值不是固定的数据类型,说起来容易做起来难。

创建多个 POJO(或“POKO”,我猜,因为我使用的是 Kotlin)是行不通的,因为这些对象可以在 other JSON 对象和类似的东西中找到。我需要一个对象,这些参数可以采用可变数据类型。那我该怎么做呢?

我是否必须在 Moshi 中编写一个自定义适配器来序列化/反序列化这些值?如果是这样,我将如何编写一个可以根据参数值分配某种数据类型的程序?或者是否有某种我可以找到/编写的 Kotlin 类/函数/扩展函数可以容纳两种可能的数据类型?

如果它是相关的,我也使用 Retrofit 2 + RxJava 2 来异步调用我的 HTTP,所以如果这些库中有任何数据类型或函数可以促进这样的事情,我会全力以赴。

即使有人只能用 Java 回答也没关系,因为我可以自己转换代码。如果我遗漏了一些明显的东西,我会提前道歉。

【问题讨论】:

我不熟悉 Moshi 或 Kotlin,但对于 Gson 和 Java,我会研究自定义反序列化器 + 标记接口(以及包装 String 的类)来处理这个问题。 interface Recordclass StringRecord implements Recordclass ObjectRecord implements Record 之类的东西,然后你的顶层有 private Record out 等。 @BenP。听起来它可能有效,但你能详细说明一下吗?我对接口的概念不是很熟悉。您所说的“包装String 的类是什么意思?谢谢。 查看我对类似问题的回答:***.com/questions/45696825/…。 “包装字符串”是指您创建自己的自定义类,该类只有一个 String 字段(类似于ResponseInteger 在我的链接答案中只有一个int 字段的方式)。至于接口,问题是您需要一个同时代表字符串和自定义对象的 type;一个界面就可以做到这一点。如果您在阅读链接后还有其他问题,请告诉我。 @BenP。这看起来很有希望,我正在尝试实现类似的东西,看看它是否有效。只有一个问题:我使用的类必须实现Parcelable,并且我需要访问它们的Creator 字段。如何将Creator 添加到界面?我已将Creator 字段添加到继承类StringRecordObjectRecord,但我必须像这样调用readTypedList:readTypedList<Record>(record, Record.Creator)--但我不能添加像 Creator 这样的静态对象到一个界面,对吧? 对变量类型列表进行打包是一个棘手的问题。也许您可以使用抽象类代替接口,然后您的Parcelable.Creator 对象可以写入和读取一些类型信息以知道是使用StringRecord 还是ObjectRecord 【参考方案1】:

你可以像我在这个答案中所做的那样:https://***.com/a/65106419/3543610

基本上,您将创建一个密封类作为您的属性inout 的类型。您还需要将原始字符串 one 包装成一个包含字符串的类型,这样您就可以使其扩展密封类,如下所示:

sealed class YourType 
    data class StringData(val value: String) : YourType()

    @JsonClass(generateAdapter = true)
    data class Record(
        val prop1: String,
        val prop2: Int
    ) : YourType()

那么具有这些属性的模型看起来就像:

@JsonClass(generateAdapter = true)
data class Model(
    ...
    val in: YourType,
    val out: YourType,
    ...
)

最后你为 YourType 类型编写自定义适配器:

class YourTypeCustomAdapter 
    @FromJson
    fun fromJson(jsonReader: JsonReader, delegate: JsonAdapter<Record>): YourType? 
        return if (jsonReader.peek() == BEGIN_OBJECT) 
            delegate.fromJson(jsonReader)
         else 
            StringData(jsonReader.nextString())
        
    

    @ToJson
    fun toJson(jsonWriter: JsonWriter, yourType: YourType, delegate: JsonAdapter<Record>) 
        when (yourType) 
            is Record -> delegate.toJson(jsonWriter, yourType)
            is StringData -> jsonWriter.value(yourType.value)
        
    

并在 Moshi 注册:

private val moshi = Moshi.Builder()
    .add(YourTypeCustomAdapter())
    .build()

【讨论】:

以上是关于使用 Moshi 反序列化可以是两种数据类型之一的字段的主要内容,如果未能解决你的问题,请参考以下文章

具有可以是两种类型之一的数据成员的类

Kotlin中Json的序列化与反序列化 -- GsonMoshi

Kotlin中Json的序列化与反序列化 -- GsonMoshi

Kotlin中Json的序列化与反序列化 -- GsonMoshi

使用 jacksonObjectMapper 将 JSON 反序列化为不同类型

Moshi 无法将 0、1 反序列化为“布尔值?”