【问题标题】:Kotlin + Arrow + Gson = None?Kotlin + Arrow + Gson = 无?
【发布时间】:2018-11-02 18:48:28
【问题描述】:

我在 Kotlin 中有一个简单的 Books and Borrowers 库的模型,如果有一本书有一个 Borrower,它就会被签出。我使用箭头选项来编码借款人的缺席/存在:

data class Borrower(val name: Name, val maxBooks: MaxBooks)
data class Book(val title: String, val author: String, val borrower: Option<Borrower> = None)

我在将这些对象序列化/反序列化到 Gson 中的 JSON 时遇到问题 - 特别是在书中将 Option&lt;Borrower&gt; 表示为 JSON null

[
  {
    "title": "Book100",
    "author": "Author100",
    "borrower": {
      "name": "Borrower100",
      "maxBooks": 100
    }
  },
  {
    "title": "Book200",
    "author": "Author200",
    "borrower": null
  }
]

我的反序列化代码:

fun jsonStringToBooks(jsonString: String): List<Book> {
    val gson = Gson()
    return try {
        gson.fromJson(jsonString, object : TypeToken<List<Book>>() {}.type)
    } catch (e: Exception) {
        emptyList()
    }
}

我得到一个空列表。几乎相同的jsonStringToBorrowers 工作正常。

有人能指出正确的方向吗?

使用不同的 JSON 库(如 kotlinx.serializationKlaxon 会更好吗?他们如何做 null &lt;-&gt; None 的事情?

谢谢!

【问题讨论】:

  • 你应该让 borrower 可以为空,然后使用支持 kotlin 可空类型的库。我总是将 moshi 与 KotlinJsonAdapterFactory 一起使用

标签: kotlin gson arrow-kt


【解决方案1】:

这个问题有点隐藏,因为您在返回空列表之前没有记录异常。如果你记录了那个异常,你会得到这个:

java.lang.RuntimeException: 无法调用没有参数的私有 arrow.core.Option()

这意味着 Gson 不知道如何创建 Option 类,因为它没有公共的空构造函数。实际上,Option 是一个密封类 (hence abstract),它有 2 个具体的子类:SomeNone。为了获得Option 的实例,您应该使用其中一种工厂方法,例如Option.just(xxx)Option.empty() 等。

现在,为了修复您的代码,您需要告诉 Gson 如何反序列化 Option 类。为此,您需要将类型适配器注册到您的 gson 对象。

可能的实现如下:

class OptionTypeAdapter<E>(private val adapter: TypeAdapter<E>) : TypeAdapter<Option<E>>() {

    @Throws(IOException::class)
    override fun write(out: JsonWriter, value: Option<E>) {
        when (value) {
            is Some -> adapter.write(out, value.t)
            is None -> out.nullValue()
        }
    }

    @Throws(IOException::class)
    override fun read(input: JsonReader): Option<E> {
        val peek = input.peek()
        return if (peek != JsonToken.NULL) {
            Option.just(adapter.read(input))
        } else {
            input.nextNull()
            Option.empty()
        }
    }

    companion object {

        fun getFactory() = object : TypeAdapterFactory {
            override fun <T> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
                val rawType = type.rawType as Class<*>
                if (rawType != Option::class.java) {
                    return null
                }
                val parameterizedType = type.type as ParameterizedType
                val actualType = parameterizedType.actualTypeArguments[0]
                val adapter = gson.getAdapter(TypeToken.get(actualType))
                return OptionTypeAdapter(adapter) as TypeAdapter<T>
            }
        }
    }

}

您可以通过以下方式使用它:

fun main(args: Array<String>) {
    val gson = GsonBuilder()
        .registerTypeAdapterFactory(OptionTypeAdapter.getFactory())
        .create()
    val result: List<Book> = try {
        gson.fromJson(json, TypeToken.getParameterized(List::class.java, Book::class.java).type)
    } catch (e: Exception) {
        e.printStackTrace()
        emptyList()
    }

    println(result)
}

该代码输出:

[Book(title=Book100, author=Author100, borrower=Some(Borrower(name=Borrower100, maxBooks=100))), Book(title=Book200, author=Author200, borrower=None)]

【讨论】:

  • 非常感谢您的周到和详细的回答。我很感激!
  • 感谢您的意见。在我看来,这个解决方案似乎只在密钥存在且为空的情况下才有效,我不希望这里也用于密钥也丢失的情况。你有什么主意吗? +
  • @ViktorLund 为此,您需要为Borrower 类型创建自己的适配器,或者有一个所有元素都具有默认值的构造函数(并使用None 作为borrower 的默认值,正如 OP 所做的那样)。这是因为 Gson 期望类有一个空的构造函数,如果不是这样,它会以“不安全”的方式创建实例(更多细节 here
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-07-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多