【问题标题】:Need to serialize Any to base64 string in Kotlin需要在 Kotlin 中将 Any 序列化为 base64 字符串
【发布时间】:2021-10-28 13:50:38
【问题描述】:

我在 Kotlin 中有以下代码,我的目标是使用它将任何实例转换为 base64 编码的字符串。同样不起作用,它会引发以下错误:

Serializer for class 'Any' is not found.\nMark the class as @Serializable or provide the serializer explicitly

我该如何解决这个问题?

class SerializerAdapter: SerializerPort {
  private val logger: Logger = LoggerFactory.getLogger(javaClass.simpleName)
  override fun toBase64(input: Any): String {
    try {
      val jsonString = Json.encodeToString(input)
      return Base64.getEncoder().encodeToString(jsonString.toByteArray())
    }catch (ex: Exception) {
      logger.error("[BASE64 Error] error converting json object to base64 encoded string: ${ex.stackTraceToString()}")
    }finally {
      return ""
    }
  }
}

【问题讨论】:

  • 您是否打算仅将toBase64() 用于带有@Serializable 注释的对象或任何对象?
  • 可能只有标有@Serializable 的那些。如果这些是唯一可兑换的,我不会。
  • 泛型在这里可能是一个不错的选择,kotlinlang.org/docs/generics.html#generic-functions
  • @undermark5 我尝试过使用泛型类型,但它要求我“重新定义”变量
  • 啊,对了,忘了,你需要在运行时拥有类型,而不仅仅是编译时。你可以做inline fun <reified T> 这样做的缺点是你只能在内联函数上具有具体的类型参数,这可能有一些缺点。另一种选择是使用 Json 库,如 Gson、Jackson、Moshi 或许多其他库中的一个,让您的 json 字符串进行编码

标签: kotlin serialization


【解决方案1】:

仅序列化Any 并不像听起来那么简单。序列化框架必须知道要序列化的数据类型。它可以使用编译类型(在您的情况下为Any)或运行时类型(提供给toBase64() 的实际类型)。这两种选择都有其缺点。由于类型擦除,运行时类型不完整,例如List<Int>List<String> 是一样的。另一方面,编译时类型可能会完全丢失,例如在泛型或像您这样的情况下。

Kotlin 序列化通常更喜欢编译类型,特别是因为reified 参数使它们更有用。可惜我们这里不能使用reified,因为toBase64()是一个虚函数,所以不能内联。

我的建议是改变这个函数的签名,额外接收KType提供的数据,然后创建内联函数以方便使用:

override fun toBase64(input: Any, type: KType): String {
    try {
        val serializer = Json.serializersModule.serializer(type)
        val jsonString = Json.encodeToString(serializer, input)
        ...
    }
}

@OptIn(ExperimentalStdlibApi::class)
inline fun <reified T>  SerializerPort.toBase64(input: Any) = toBase64(input, typeOf<T>())

或者,我们可以使用运行时类型进行序列化,但请注意我之前提到的问题 - 它可能不适用于泛型。

val serializer = Json.serializersModule.serializer(input::class.starProjectedType)
val jsonString = Json.encodeToString(serializer, input)

【讨论】:

  • 使用最后两行时出现此错误:不允许在类型参数中使用星形投影,但有 java.util.ArrayList\n\tat kotlinx.serialization.SerializersKt__SerializersKt.serializerByKTypeImpl$ SerializersKt__SerializersKt
  • 是因为我用的是列表吗?
  • 当我传递单个项目时它工作得很好
  • 是的,这正是我的意思。如果您将toBase64()ArrayList 一起使用,则无法知道列表中的类型是什么。编译类型只是Any,运行时类型只是ArrayList。一些序列化框架可能能够处理这种情况,只需获取第一项,然后检查其运行时类型。我认为 Kotlin 序列化不会这样做。你可以试试杰克逊,但老实说,我认为我们应该完全避免这种情况。最好知道我们序列化了什么,而不是“猜测”它。
  • 无论如何,第一个解决方案应该可以正常使用ArrayList。如果你只能更改toBase64()签名。
【解决方案2】:

kotlinx.serialization 是一个编译时 库,因此您必须预先知道要序列化的每个对象。

取决于您的用例;

  • 您可能需要使用使用反射的 runtime 库(例如众多 JSON 或 XML 序列化程序之一)才能使用不属于您自己的类。
  • 或者,如果您的要求更多是希望能够序列化其他人的类,那么您可以使用其他人必须实现的自己的接口以及@Serializable(请参阅https://github.com/Kotlin/kotlinx.serialization/issues/1005)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-06-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-24
    • 2020-03-16
    • 1970-01-01
    相关资源
    最近更新 更多