Котлин + Стрелка + Гсон = Нет?
У меня есть модель в Kotlin простой библиотеки Книг и Заемщиков, где Книга проверена, если у нее есть Заемщик. Я использую Arrow Option для кодирования отсутствия / присутствия Заемщика:
data class Borrower(val name: Name, val maxBooks: MaxBooks)
data class Book(val title: String, val author: String, val borrower: Option<Borrower> = None)
У меня возникли проблемы с сериализацией / десериализацией этих объектов в / из JSON в Gson - в частности, представление Option<Borrower>
в 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.serialization
или же Klaxon
быть лучшей идеей и как они делают null <-> None
вещь?
Спасибо!
1 ответ
Эта проблема немного скрыта тем, что вы не регистрируете исключение перед возвратом пустого списка. Если бы вы зарегистрировали это исключение, вы бы получили это:
java.lang.RuntimeException: не удалось вызвать закрытый arrow.core.Option() без аргументов
Это означает, что Гсон не знает, как создать Option
класс, потому что у него нет открытого пустого конструктора. В самом деле, Option
является запечатанным классом ( следовательно, абстрактным), имеющим 2 конкретных дочерних класса: Some
а также None
, Для того, чтобы получить экземпляр 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)]