3

我想要一个父接口/抽象类上的方法,它利用泛型方法传入实现类的类。

interface Domain {
    fun toJSON(): String { return Json.encodeToString(this) }
}

@Serializable
class User: Domain {
    val a: Int
}

这不起作用,因为Json.encodeToString不知道“this”的类别。

@Serializable似乎实现KSerializer了,所以理论上我可以要求域从它继承,但该接口是模板化的。并且标记实现类@Serializable似乎KSerializer直到编译时才实现,因此会产生错误。

如何实现此toJSON()方法或告诉 Domain 它的实现者必须是@Serializable/ KSerializer

我也试过:

interface Domain<T> {
    fun toJSON(): String { return Json.encodeToString(this) }
}

@Serializable
class User: Domain<User> {
    val a: Int
}

但这会导致:

kotlin.IllegalStateException: Only KClass supported as classifier, got T

所有这一切的另一个复杂因素是我试图在 KMM 中执行此操作。

4

3 回答 3

2

Json.encodeToString使用一个具体化的泛型来访问参数的类。这意味着它使用参数的声明类型(在编译时已知),这里是Domain.

最简单的方法是只使用您自己的通用具体化扩展函数来更精确地捕获接收器的声明类:

interface Domain

inline fun <reified T : Domain> T.toJSON(): String = Json.encodeToString(this)

@Serializable
data class User(
    val name: String,
    val age: Int,
): Domain

fun main() {
    val user = User("Bob", 35)
    println(user.toJSON())
}

但是请注意,这会遇到同样的问题:如果您尝试在使用Domaintype 声明的变量上使用此扩展,它仍然不会尝试访问运行时类型:

val user: Domain = User("Bob", 35)
println(user.toJSON()) // still a problem here

如果您想实际使用动态类型,则可以像@broot在他的回答中提到的那样动态访问序列化程序。

于 2021-10-12T09:48:48.043 回答
2

@Serializable 似乎实现了 KSerializer

这不是真的。@Serializable是一个注释,它指示注释处理器生成(除其他外)一个方法(用于伴随对象),该方法返回此类serializer()的实例。KSerializer它不会为类本身添加新接口,因此您无法告诉类型系统它应该检查什么。

如果找不到序列化程序,您将收到运行时错误:

import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json

class HasNoSerializableAnnotation

fun main() {
    // Compiles fine, but will throw runtime exception
    // kotlinx.serialization.SerializationException: Serializer for class 'HasNoSerializableAnnotation' is not found.
    Json.encodeToString(HasNoSerializableAnnotation()) 
}

这是有意的,因为可以在没有插件生成的序列化程序的情况下序列化类的实例(即使您没有手动将自定义序列化程序encodeToXXX作为另一个参数传递给方法 - 通过在 中传递序列化程序(作为上下文)serializersModule)。

我如何实现这个 toJSON() 方法或告诉 Domain 它的实现者必须是 @Serializable

没有办法做到这一点。你可以拥有这个 API,但它很容易出现运行时错误:

interface Domain
inline fun <reified T : Domain> T.toJSON() = Json.encodeToString(this)

我如何实现这个 toJSON() 方法或告诉 Domain 它的实现者必须是 KSerializer

实际上,这里不需要完整KSerializer- 只是它的序列化部分:

interface Domain
inline fun <reified T : Domain> T.toJSON(serializer: SerializationStrategy<T>) = 
    Json.encodeToString(serializer,this)
于 2021-10-12T10:06:25.223 回答
2

它不起作用,因为encodeToString()在编译时解析类型,而不是在运行时(它使用具体类型)。

也许有更好的方法,但是您可以通过手动获取序列化程序来做到这一点,在运行时解析类型:

fun toJSON(): String {
    val serializer = Json.serializersModule.serializer(this::class.createType())
    return Json.encodeToString(serializer, this)
}

请注意,这仅适用于 JVM。如果您需要在 KMM 中执行此操作,请注意多平台反射很复杂和/或需要一些时间才能成熟。kotlinx.serialization提供了一种方法来做到这一点,但有关于平台之间可能的不一致行为的警告:

@OptIn(InternalSerializationApi::class)
fun toJSON(): String {
    @Suppress("UNCHECKED_CAST")
    val serializer = this::class.serializer() as KSerializer<Any?>
    return Json.encodeToString(serializer, this)
}
于 2021-10-12T09:48:38.630 回答