Как сериализовать / десериализовать закрытый класс Kotlin? - PullRequest
0 голосов
/ 03 мая 2018

У меня есть следующий запечатанный класс:

sealed class ViewModel {

  data class Loaded(val value : String) : ViewModel()
  object Loading : ViewModel()

}

Как я могу сериализовать / десериализовать экземпляры класса ViewModel, скажем, в / из формата JSON?

Я пытался использовать библиотеку Genson serializer / deserializer - она ​​может обрабатывать классы данных Kotlin, также возможна поддержка полиморфных типов (например, использование некоторых метаданных для указания конкретных типов).

Однако библиотека не работает на типах Kotlin object, так как это синглтоны без открытого конструктора. Я думаю, я мог бы написать собственный конвертер Genson, чтобы справиться с этим, но, может быть, есть более простой способ сделать это?

Ответы [ 3 ]

0 голосов
/ 24 мая 2018

В итоге я реализовал специальный конвертер и фабрику, чтобы правильно подключить его к Genson.

Он использует соглашение метаданных Генсона для представления объекта в виде:

{ 
  "@class": "com.example.ViewModel.Loading" 
}

Конвертер предполагает, что useClassMetadata установлен флаг, поэтому при сериализации нужно просто отметить пустой объект. Для десериализации он разрешает имя класса из метаданных, загружает его и получает objectInstance .

object KotlinObjectConverter : Converter<Any> {

override fun serialize(objectData: Any, writer: ObjectWriter, ctx: Context) {
    with(writer) {
        // just empty JSON object, class name will be automatically added as metadata
        beginObject()
        endObject()
    }
}

override fun deserialize(reader: ObjectReader, ctx: Context): Any? =
    Class.forName(reader.nextObjectMetadata().metadata("class"))
        .kotlin.objectInstance
        .also { reader.endObject() }
}

Чтобы убедиться, что этот преобразователь применяется только к фактическим объектам , я зарегистрировал его, используя фабрику, которая сообщает Genson, когда его использовать и когда следует вернуться к реализации по умолчанию.

object KotlinConverterFactory : Factory<Converter<Any>> {

    override fun create(type: Type, genson: Genson): Converter<Any>? =
        if (TypeUtil.getRawClass(type).kotlin.objectInstance != null) KotlinObjectConverter
        else null

}

Фабрику можно использовать для настройки Genson через builder:

GensonBuilder()
        .withConverterFactory(KotlinConverterFactory)
        .useClassMetadata(true) // required to add metadata during serialization
        // some other properties
        .create()

Код, возможно, мог бы быть еще лучше с функцией цепных преобразователей, но у меня еще не было времени проверить это.

0 голосов
/ 29 января 2019

У меня недавно была похожая проблема (хотя я использовал Джексона, а не Дженсона.)

При условии, что у меня есть следующее:

sealed class Parent(val name: String)

object ChildOne : Parent("ValOne")
object ChildTwo : Parent("ValTwo")

Затем добавление функции JsonCreator в запечатанный класс:

sealed class Parent(val name: String) {

    private companion object {
        @JsonCreator
        @JvmStatic
        fun findBySimpleClassName(simpleName: String): Parent? {
            return Parent::class.sealedSubclasses.first {
                it.simpleName == simpleName
            }.objectInstance
        }
    }
}

Теперь вы можете десериализовать, используя ChildOne или ChildTwo как key в свойстве json.

0 голосов
/ 03 мая 2018

Возможно, вы правы в создании собственного сериализатора.

Я пытался сериализовать и десериализовать ваш класс с помощью библиотеки Джексона и Kotlin.

Это зависимости Maven для Джексона:

<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.8.8</version>
</dependency>

<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.8.8</version>
</dependency>

Вы можете сериализовать запечатанный класс в JSON, используя эту библиотеку без дополнительных пользовательских сериализаторов, но десериализация требует специального десериализатора.

Ниже приведен код игрушки, который я использовал для сериализации и десериализации вашего запечатанного класса:

import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.JsonDeserializer
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.module.SimpleModule

sealed class ViewModel {
    data class Loaded(val value: String) : ViewModel()
    object Loading : ViewModel()
}

// Custom serializer
class ViewModelDeserializer : JsonDeserializer<ViewModel>() {
    override fun deserialize(jp: JsonParser?, p1: DeserializationContext?): ViewModel {
        val node: JsonNode? = jp?.getCodec()?.readTree(jp)
        val value = node?.get("value")
        return if (value != null) ViewModel.Loaded(value.asText()) else ViewModel.Loading
    }
}

fun main(args: Array<String>) {
    val m = createCustomMapper()
    val ser1 = m.writeValueAsString(ViewModel.Loading)
    println(ser1)
    val ser2 = m.writeValueAsString(ViewModel.Loaded("test"))
    println(ser2)
    val deserialized1 = m.readValue(ser1, ViewModel::class.java)
    val deserialized2 = m.readValue(ser2, ViewModel::class.java)
    println(deserialized1)
    println(deserialized2)
}

// Using mapper with custom serializer
private fun createCustomMapper(): ObjectMapper {
    val m = ObjectMapper()
    val sm = SimpleModule()
    sm.addDeserializer(ViewModel::class.java, ViewModelDeserializer())
    m.registerModule(sm)
    return m
}

Если вы запустите этот код, это будет вывод:

{}
{"value":"test"}
ViewModel$Loading@1753acfe
Loaded(value=test)
...