【问题标题】:How to encode a raw, unescaped JSON string?如何编码原始的、未转义的 JSON 字符串?
【发布时间】:2023-01-27 19:11:48
【问题描述】:

我有一个 Kotlin 服务器充当网关,使用 JSON 通过 REST API 处理客户端和许多支持服务之间的通信。我的服务器使用 Kotlinx Serialization 进行序列化。

通常我需要解析和调整来自支持服务的响应,但偶尔我只想返回原始 JSON 内容作为响应。

例如:

import kotlinx.serialization.json.*

fun main() {
  // I get some JSON from a backing service
  val backingServiceResponse = """
    {"some":"json",id:123,content:[]}
  """.trimIndent()

  // I create a response object, that I will return to the client
  val packet = ExampleClientResponse("name", backingServiceResponse)

  val encodedPacket = Json.encodeToString(packet)

  println(encodedPacket)

  // I expect that the JSON is encoded without quotes
  require("""{"name":"name","content":{"some":"json",id:123,content:[]}}""" == encodedPacket)
}

@Serializable
data class ExampleClientResponse(
  val name: String,
  val content: String, // I want this to be encoded as unescaped JSON
)

然而,content 的值被引号包围,并且被转义

{
  "name":"name",
  "content":"{\"some\":\"json\",id:123,content:[]}"
}

我想要的是对 content 属性进行字面编码:

{
  "name":"name",
  "content":{
    "some":"json",
    "id":123,
    "content":[
      
    ]
  }
}

我正在使用 Kotlin 1.8.0 和 Kotlinx Serialization 1.4.1。

【问题讨论】:

    标签: json kotlin kotlinx.serialization


    【解决方案1】:

    在 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))
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-02-17
      • 1970-01-01
      • 1970-01-01
      • 2013-09-08
      • 2022-08-03
      • 2018-07-01
      • 1970-01-01
      相关资源
      最近更新 更多