kotlin序列化用于通用对象...?

发布于 2025-02-13 23:51:39 字数 3274 浏览 0 评论 0 原文

我有一个实现通用对象并试图序列化 /进行序列化的好主意,并收到了此错误:

Serializer for class 'DetailsRequest' is not found.
Mark the class as @Serializable or provide the serializer explicitly.

我认为@Serializer注释将实现这一目标...?

数据类的实现及其自定义序列化器(可能是过度杀伤)如下:

@Serializable(with=DetailsRequestSerializer::class)
data class DetailsRequest<out T>(val details: T)

object DetailsRequestSerializer : KSerializer<DetailsRequest<*>> {
    override val descriptor: SerialDescriptor = buildClassSerialDescriptor(
        "DetailsRequest") {
        when (this::class) {
            String::class -> element<String>("details")
            Long::class -> element<Long>("details")
            Int::class -> element<Int>("details")
            Double::class -> element<Double>("details")
            Float::class -> element<Float>("details")
            else -> element<String>("details")
        }
    }

    override fun serialize(
        encoder: Encoder,
        value: DetailsRequest<*>
    ) {
        value.details.let {
            encoder.encodeStructure(descriptor) {
                when (value::class) {
                    String::class -> encodeStringElement(descriptor, 0, value.details as String)
                    Long::class -> encodeLongElement(descriptor, 0, value.details as Long)
                    Int::class -> encodeIntElement(descriptor, 0, value.details as Int)
                    Double::class -> encodeDoubleElement(descriptor, 0, value.details as Double)
                    Float::class -> encodeFloatElement(descriptor, 0, value.details as Float)
                    else -> encodeStringElement(descriptor, 0, value.details as String)
                }
            }
        }
    }

    override fun deserialize(decoder: Decoder): DetailsRequest<*> {
        return when (this::class) {
            String::class -> DetailsRequest(decoder.decodeString())
            Long::class -> DetailsRequest(decoder.decodeLong())
            Int::class -> DetailsRequest(decoder.decodeInt())
            Double::class -> DetailsRequest(decoder.decodeDouble())
            Float::class -> DetailsRequest(decoder.decodeFloat())
            else -> DetailsRequest(decoder.decodeString())
        }
    }
}

我编写了本单元测试:

class PlaygroundTest {
    private val json = Json {
        encodeDefaults = true
        isLenient = true
        allowSpecialFloatingPointValues = true
        allowStructuredMapKeys = true
        prettyPrint = true
        useArrayPolymorphism = false
        ignoreUnknownKeys = true
    }

    @Test
    fun `Details Integration Tests`() {
        val input = DetailsRequest(details = "New Record")
        val value = json.encodeToString(input)
        println("value=[$value]")
        val output = value.convertToDataClass<DetailsRequest<String>>()
        println("output=[$output]")
    }

    @OptIn(InternalSerializationApi::class)
    internal inline fun <reified R : Any> String.convertToDataClass() =
        json.decodeFromString(R::class.serializer(), this)
}

奖励积分:这是一个Kotlin Multiplatform项目,我认为这不会影响这种情况。

这种情况甚至可能吗?

I had the bright idea to implement a generic object and attempt to serialize / deserialize it, and received this error:

Serializer for class 'DetailsRequest' is not found.
Mark the class as @Serializable or provide the serializer explicitly.

I thought the @Serializer annotation would have achieved this... ???

The implementation of the data class, and its custom serializer (which is probably overkill), are as follows:

@Serializable(with=DetailsRequestSerializer::class)
data class DetailsRequest<out T>(val details: T)

object DetailsRequestSerializer : KSerializer<DetailsRequest<*>> {
    override val descriptor: SerialDescriptor = buildClassSerialDescriptor(
        "DetailsRequest") {
        when (this::class) {
            String::class -> element<String>("details")
            Long::class -> element<Long>("details")
            Int::class -> element<Int>("details")
            Double::class -> element<Double>("details")
            Float::class -> element<Float>("details")
            else -> element<String>("details")
        }
    }

    override fun serialize(
        encoder: Encoder,
        value: DetailsRequest<*>
    ) {
        value.details.let {
            encoder.encodeStructure(descriptor) {
                when (value::class) {
                    String::class -> encodeStringElement(descriptor, 0, value.details as String)
                    Long::class -> encodeLongElement(descriptor, 0, value.details as Long)
                    Int::class -> encodeIntElement(descriptor, 0, value.details as Int)
                    Double::class -> encodeDoubleElement(descriptor, 0, value.details as Double)
                    Float::class -> encodeFloatElement(descriptor, 0, value.details as Float)
                    else -> encodeStringElement(descriptor, 0, value.details as String)
                }
            }
        }
    }

    override fun deserialize(decoder: Decoder): DetailsRequest<*> {
        return when (this::class) {
            String::class -> DetailsRequest(decoder.decodeString())
            Long::class -> DetailsRequest(decoder.decodeLong())
            Int::class -> DetailsRequest(decoder.decodeInt())
            Double::class -> DetailsRequest(decoder.decodeDouble())
            Float::class -> DetailsRequest(decoder.decodeFloat())
            else -> DetailsRequest(decoder.decodeString())
        }
    }
}

I have written this unit test:

class PlaygroundTest {
    private val json = Json {
        encodeDefaults = true
        isLenient = true
        allowSpecialFloatingPointValues = true
        allowStructuredMapKeys = true
        prettyPrint = true
        useArrayPolymorphism = false
        ignoreUnknownKeys = true
    }

    @Test
    fun `Details Integration Tests`() {
        val input = DetailsRequest(details = "New Record")
        val value = json.encodeToString(input)
        println("value=[$value]")
        val output = value.convertToDataClass<DetailsRequest<String>>()
        println("output=[$output]")
    }

    @OptIn(InternalSerializationApi::class)
    internal inline fun <reified R : Any> String.convertToDataClass() =
        json.decodeFromString(R::class.serializer(), this)
}

Bonus points: This is a Kotlin Multiplatform project, which I do not believe will impact this situation.

Is this type of situation even possible?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

够钟 2025-02-20 23:51:39

您询问的有关好消息的问题

:这种情况是完全可能的,您的代码几乎是正确的。但是,Kotlin的内置序列化对获得连续化器有些奇怪,因此起初您的代码片段不起作用,这有点令人惊讶。

答案在 [1]

约束

本段解释了Serializer()实现的已知(但不是全部!)约束。请注意,它们不是错误,而是我们无法解决的实施限制。

[...]

  • 具有通用参数类的类的序列化被此方法忽略

具有 代码>详细信息request :: class.serializer()无法使用。

幸运的是, kotlinx.serialization 向我们提供另一种方法 [2] to获取一个任意类型的序列化器:

inline fun <T> serializer(): KSerializer<T>

在您的情况下,您可以这样使用它:

// OptIn no longer needed
internal inline fun <reified R : Any> String.convertToDataClass() =
    json.decodeFromString(serializer<R>(), this)

请注意, json.decodefromstring 自动使用serializer()自动使用 json.decodefromstring

internal inline fun <reified R : Any> String.convertToDataClass() =
    json.decodeFromString<R>(this)

。 ,在这一点上,您甚至可以考虑完全删除转换todataclass方法。)

您没有询问

是否使用该更改来运行代码的问题,您会遇到不同的错误:

Exception in thread "main" kotlinx.serialization.json.internal.JsonDecodingException: Unexpected JSON token at offset 0: Expected beginning of the string, but got {
JSON input: {
    "details": "New Record"
}

这仅仅是因为您的实现Deserializer的中有点不合时宜 - 它试图将delesialize详细信息作为单个字符串(例如“新记录” ),而不是按照您序列化的方式(例如 {例如 { “详细信息”:“新记录”} )。

对此问题的完整说明将需要比此答案合理的空间更多的空间,但是我将您引用了非常完整的(如果有点长) - 序列化指南在这里。您可能需要滚动并阅读其余的“自定义序列化器”部分,以确保您也了解这一切。

更快的解决方案

(如果您将标记详细信息)使用 @serializable (没有参数的,则参数),然后删除整个 efoce> efoce> efacterrequestSerialializer 对象,代码将正常运行良好,序列化并进行序列化,如人们所期望的。 @serializable 注释(没有参数的)告诉编译器自动化序列化器,在这种情况下,它成功合理地进行了序列化器。

除非您有特定的序列化需要此默认序列化器无法实现,否则我强烈建议您简单地使用默认序列化器。它更干净,工作要少得多。 :-)

[1] 您必须向下滚动一点; Dokka不允许我链接到功能的特定声明。相关位一直处于底部。

[2] 是的,它在文档中的同一页面上。只是不要滚动这次。 : - )

The problem you asked about

Good news: this situation is totally possible, and your code is very nearly correct. Kotlin's builtin serialization is a bit wonky about getting serializers, though, so it is a bit surprising at first that your code snippet doesn't work.

The answer lies in a bit of almost fine print in the documentation[1]:

Constraints

This paragraph explains known (but not all!) constraints of the serializer() implementation. Please note that they are not bugs, but implementation restrictions that we cannot workaround.

[...]

  • Serializers for classes with generic parameters are ignored by this method

Since DetailsRequest<out T> has that generic out T, this means that accessing its serializer through DetailsRequest::class.serializer() won't work.

Fortunately, kotlinx.serialization provides us a different method[2] to get a serializer for an arbitrary type:

inline fun <T> serializer(): KSerializer<T>

In your case, you could use it like this:

// OptIn no longer needed
internal inline fun <reified R : Any> String.convertToDataClass() =
    json.decodeFromString(serializer<R>(), this)

Note that there's an overload of json.decodeFromString that uses serializer() automatically: all you really need is

internal inline fun <reified R : Any> String.convertToDataClass() =
    json.decodeFromString<R>(this)

(In fact, at this point, you may even consider removing the convertToDataClass method entirely.)

The problem you didn't ask about

If you run the code with that change, you'll get a different error:

Exception in thread "main" kotlinx.serialization.json.internal.JsonDecodingException: Unexpected JSON token at offset 0: Expected beginning of the string, but got {
JSON input: {
    "details": "New Record"
}

This is simply because your implementation of the deserializer is a bit off—it's trying to deserialize DetailsRequest as a single string (such as "New Record") rather than a full JSON object the way you serialized it (such as { "details": "New Record" }).

A full explanation of the issue here would take a bit more space than is reasonable for this answer, but I refer you to the very complete—if a bit long—serialization guide here. You may need to scroll up and read the rest of the Custom Serializers section to ensure you understand it all as well.

A quicker solution

If you mark DetailsRequest with simply @Serializable (without the with argument) and remove the entire DetailsRequestSerializer object, the code will run fine and serialize and deserialize as one would expect. The @Serializable annotation (without a with argument) tells the compiler to autogenerate a serializer, which it does successfully and reasonably in this case.

Unless you have a specific serialization need that this default serializer does not fulfill, I would strongly recommend simply using the default one. It's much cleaner and a lot less work. :-)

[1] You'll have to scroll down a bit; Dokka doesn't allow me to link to a specific declaration of a function. The relevant bit is all the way at the bottom.

[2] Yes, it's on the same page in the documentation. Just don't scroll down this time. :-)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文