【问题标题】:Parsing empty object with kotlinx.serialization使用 kotlinx.serialization 解析空对象
【发布时间】:2019-06-11 10:00:57
【问题描述】:

我很难理解如何使用实验性的 kotlinx.serialization 库解析空对象 {}。事实上,当 API 响应可能是其中之一时,就会出现复杂情况;

{
  "id": "ABC1",
  "status": "A_STATUS"
}

{}

我用作序列化器的数据结构是;

data class Thing(val id: String = "", val status: String = "")

这带有@kotlinx.serialization.Serializable 注释,并在 API 客户端库中用于在原始 API 响应和数据模型之间进行编组。默认值告诉序列化库该字段是可选的,并替换了 Kotlin 1.3.30 之前的 @Optional 方法。

最后,我正在使用的kotlinx.serialization.json.Json 解析器具有使用nonstrict 模板应用的配置。

如何定义一个可以解析空对象和kotlinx.serialization 的预期数据类型的序列化程序?我需要编写自己的KSerialiser 还是缺少配置。理想情况下,空对象应该被忽略/解析为null?

使用Thing 数据类解析空对象时出现的错误是;

Field 'id' is required, but it was missing

【问题讨论】:

标签: json kotlin kotlinx.serialization


【解决方案1】:

所以这归结为 kotlinCompilerClasspath 具有不同版本的 kotlin(1.3.21,而不是 1.3.31)。

有趣的是,这是由于我在配置我的 gradle 插件项目时遵循的建议kotlin-dsl 插件指定版本。

明确依赖我需要的版本修复了kotlinx.serialisation 行为(主线代码没有更改)

【讨论】:

    【解决方案2】:

    是的,理想情况下 null 而不是 {} 解析起来更方便,但有时您只需要使用后端发送给您的内容

    我想到了两种解决方案。

    1. 更简单,具体到您使用地图的情况:
    import kotlinx.serialization.decodeFromString
    import kotlinx.serialization.json.Json
    import org.junit.Assert.assertEquals
    import org.junit.Assert.assertTrue
    import org.junit.Test
    
    class ThingMapSerializerTest {
    
        @Test
        fun `should deserialize to non empty map`() {
            val thingMap: Map<String, String> =
                Json.decodeFromString("""{"id":"ABC1","status":"A_STATUS"}""")
    
            assertTrue(thingMap.isNotEmpty())
            assertEquals("ABC1", thingMap["id"])
            assertEquals("A_STATUS", thingMap["status"])
        }
    
        @Test
        fun `should deserialize to empty map`() {
            val thingMap: Map<String, String> = Json.decodeFromString("{}")
    
            assertTrue(thingMap.isEmpty())
        }
    }
    
    1. 更复杂但更通用,适用于值类型的任何组合。我推荐具有显式空值的密封类,而不是具有空默认值的数据类:
    import kotlinx.serialization.KSerializer
    import kotlinx.serialization.SerializationException
    import kotlinx.serialization.descriptors.SerialDescriptor
    import kotlinx.serialization.descriptors.buildClassSerialDescriptor
    import kotlinx.serialization.descriptors.serialDescriptor
    import kotlinx.serialization.encoding.CompositeDecoder
    import kotlinx.serialization.encoding.Decoder
    import kotlinx.serialization.encoding.Encoder
    import kotlinx.serialization.encoding.decodeStructure
    import kotlinx.serialization.json.Json
    import org.junit.Assert.assertEquals
    import org.junit.Test
    
    class ThingSerializerTest {
    
        @Test
        fun `should deserialize to thing`() {
            val thing: OptionalThing =
                Json.decodeFromString(
                    OptionalThing.ThingSerializer,
                    """{"id":"ABC1","status":"A_STATUS"}"""
                )
    
            assertEquals(OptionalThing.Thing(id = "ABC1", status = "A_STATUS"), thing)
        }
    
        @Test
        fun `should deserialize to empty`() {
            val thing: OptionalThing =
                Json.decodeFromString(OptionalThing.ThingSerializer, "{}")
    
            assertEquals(OptionalThing.Empty, thing)
        }
    
        sealed class OptionalThing {
            data class Thing(val id: String = "", val status: String = "") : OptionalThing()
            object Empty : OptionalThing()
    
            object ThingSerializer : KSerializer<OptionalThing> {
                override val descriptor: SerialDescriptor =
                    buildClassSerialDescriptor("your.app.package.OptionalThing") {
                        element("id", serialDescriptor<String>(), isOptional = true)
                        element("status", serialDescriptor<String>(), isOptional = true)
                    }
    
                override fun deserialize(decoder: Decoder): OptionalThing {
                    decoder.decodeStructure(descriptor) {
                        var id: String? = null
                        var status: String? = null
                        loop@ while (true) {
                            when (val index = decodeElementIndex(descriptor)) {
                                CompositeDecoder.DECODE_DONE -> break@loop
                                0 -> id = decodeStringElement(descriptor, index = 0)
                                1 -> status = decodeStringElement(descriptor, index = 1)
                                else -> throw SerializationException("Unexpected index $index")
                            }
                        }
                        return if (id != null && status != null) Thing(id, status)
                        else Empty
                    }
                }
    
                override fun serialize(encoder: Encoder, value: OptionalThing) {
                    TODO("Not implemented, not needed")
                }
            }
        }
    
    }
    

    当 'Thing' 是 json 对象中的一个字段时:

      "thing":{"id":"ABC1","status":"A_STATUS"} // could be {} 
    

    你可以这样注释属性:

    @Serializable(with = OptionalThing.ThingSerializer::class)
    val thing: OptionalThing
    
    

    测试:

    • classpath "org.jetbrains.kotlin:kotlin-serialization:1.4.10"
    • implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.1"

    【讨论】:

    • 这些都不需要。正如OP所期望的,当Thing("", "")被序列化时,它应该被序列化为{}when defaults are not encoded,这是JSON编码器的默认设置。
    猜你喜欢
    • 2019-08-20
    • 1970-01-01
    • 2019-09-04
    • 1970-01-01
    • 1970-01-01
    • 2015-03-14
    • 2023-03-11
    • 2018-07-13
    • 1970-01-01
    相关资源
    最近更新 更多