在 Kotlinx Serialization 1.5.0-RC 中编码原始 JSON 是可能的,它于 2023 年 1 月 26 日发布,并且是实验性的。这在早期版本中是不可能的。
创建自定义序列化程序
首先,创建一个自定义序列化程序,RawJsonStringSerializer,它将编码/解码字符串。
解码不需要特别的努力,并且会按原样工作。
但是,如果我们将字符串编码为 JSON,编码需要使用新的 JsonUnquotedLiteral function 来编码内容
import kotlinx.serialization.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*
import kotlinx.serialization.json.*
@OptIn(ExperimentalSerializationApi::class)
private object RawJsonStringSerializer : KSerializer<String> {
override val descriptor = PrimitiveSerialDescriptor("my.project.RawJsonString", PrimitiveKind.STRING)
/** Decodes a [String] as-is, no special treatment required */
override fun deserialize(decoder: Decoder): String = decoder.decodeString()
/**
* Encodes [value] using [JsonUnquotedLiteral], if [encoder] is a [JsonEncoder],
* or with [Encoder.encodeString] otherwise.
*/
override fun serialize(encoder: Encoder, value: String) = when (encoder) {
is JsonEncoder -> encoder.encodeJsonElement(JsonUnquotedLiteral(value))
else -> encoder.encodeString(value)
}
}
应用自定义序列化程序
现在在您的课堂上,您可以使用 @Serializable(with = ...) 注释 content 以使用新的序列化程序。
import kotlinx.serialization.*
@Serializable
data class ExampleClientResponse(
val name: String,
@Serializable(with = RawJsonStringSerializer::class)
val content: String,
)
结果
main 方法中没有任何变化 - Kotlinx 序列化将按字面意思自动编码 content,因此它现在会成功。
import kotlinx.serialization.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*
import kotlinx.serialization.json.*
fun main() {
val backingServiceResponse = """
{"some":"json",id:123,content:[]}
""".trimIndent()
val packet = ExampleClientResponse("name", backingServiceResponse)
val encodedPacket = Json.encodeToString(packet)
println(encodedPacket)
require("""{"name":"name","content":{"some":"json",id:123,content:[]}}""" == encodedPacket)
}
减少重复
使用 @Serializable(with = ...) 在开关用法时很简单,但是如果您有很多属性要编码为文字 JSON 怎么办?
类型别名序列化
When a fix is released 在 Kotlin 1.8.20 中,这将通过单行代码实现
// awaiting fix https://github.com/Kotlin/kotlinx.serialization/issues/2083
typealias RawJsonString = @Serializable(with = RawJsonStringSerializer::class) String
@Serializable
data class ExampleClientResponse(
val name: String,
val content: RawJsonString, // will be encoded literally, without escaping
)
原始编码值类
在 Kotlinx 序列化可以处理编码类型别名原语之前,您可以使用 inline value class,我们告诉 Kotlinx 序列化使用 RawJsonStringSerializer 进行编码。
@JvmInline
@Serializable(with = RawJsonStringSerializer::class)
value class RawJsonString(val content: String) : CharSequence by content
现在数据类中需要注释:
@Serializable
data class ExampleClientResponse(
val name: String,
val content: RawJsonString, // will be encoded as a literal JSON string
)
RawJsonStringSerializer 需要更新以包装/解包值类
@OptIn(ExperimentalSerializationApi::class)
private object RawJsonStringSerializer : KSerializer<RawJsonString> {
override val descriptor = PrimitiveSerialDescriptor("my.project.RawJsonString", PrimitiveKind.STRING)
override fun deserialize(decoder: Decoder): RawJsonString = RawJsonString(decoder.decodeString())
override fun serialize(encoder: Encoder, value: RawJsonString) = when (encoder) {
is JsonEncoder -> encoder.encodeJsonElement(JsonUnquotedLiteral(value.content))
else -> encoder.encodeString(value.content)
}
}
权衡是转换到新值类或从新值类转换有点笨拙。
val backingServiceResponse = """
{"some":"json",id:123,content:[]}
""".trimIndent()
// need to wrap backingServiceResponse in the RawJsonString value class
val packet = ExampleClientResponse("name", RawJsonString(backingServiceResponse))