在使用实现者的接口中实现泛型方法 class

Implementing generic method in interface that uses implementors class

我想要一个父接口/抽象 Class 上的方法,该方法利用泛型方法传入实现 class.

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

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

这不起作用,因为 Json.encodeToString 不知道 'this' 的 class。

@Serializable 似乎实现了 KSerializer 所以理论上我可以要求域从它继承,但该接口是模板化的。并且标记实现 class @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 中执行此操作。

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

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

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

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

@OptIn(InternalSerializationApi::class)
fun toJSON(): String {
    @Suppress("UNCHECKED_CAST")
    val serializer = this::class.serializer() as KSerializer<Any?>
    return Json.encodeToString(serializer, this)
}

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

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

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())
}

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

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

如果你想实际使用动态类型,你可以像@broot 提到的那样动态访问序列化程序

@Serializable seems to implement KSerializer

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

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

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()) 
}

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

中的序列化程序(作为上下文)

How do I implement this toJSON() method or tell Domain that its implementers must be @Serializable

没有办法做到这一点。您可以使用此 API,但它容易出现运行时错误:

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

How do I implement this toJSON() method or tell Domain that its implementers must be KSerializer

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

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