Kotlin使用JacksonPubSubMessageConverter发送Raw JSON双重编码如何解决
你遇到的双重编码问题本质是JacksonPubSubMessageConverter的默认逻辑导致的:当你传入字符串类型的payload时,converter会把它当成普通Java/Kotlin对象调用objectMapper.writeValueAsBytes()做JSON序列化,字符串类型在JSON序列化时会自动包裹双引号、转义内部的双引号,最终输出的字节就是转义后的双重编码结果。
你给出的示例代码逻辑刚好复现了这个过程:已经序列化好的JSON字符串被再次做JSON序列化,导致最终的字节内容无法直接反序列化为TestClass对象。
方案1:直接传入业务对象(最推荐)
JacksonPubSubMessageConverter设计初衷就是自动完成业务对象到JSON字节的序列化,你不需要自己提前把对象转成JSON字符串,直接把业务对象实例传给PubSubTemplate的发送方法即可:
// 直接构造业务对象作为payload val payload = TestClass(value = 42) // 传入PubSubTemplate发送,converter会自动生成正确的{"value":42}字节 pubSubTemplate.publish("你的topic名称", payload)
消费方拿到的payload字节就是标准JSON格式,可以直接反序列化为TestClass。
方案2:传入字节类型payload,绕过JSON序列化
如果你确实需要自己构造原始JSON字符串,不需要Jackson帮你做序列化,可以把JSON字符串转成ByteArray/ByteString类型再传入,JacksonPubSubMessageConverter默认不会对字节类型的payload做二次处理,会直接写入PubSub消息:
import java.nio.charset.StandardCharsets // 自己构造的原始JSON字符串 val rawJson = "{ \"value\": 42 }" // 转成UTF-8字节数组作为payload val payload = rawJson.toByteArray(StandardCharsets.UTF_8) // 直接发送字节数组即可 pubSubTemplate.publish("你的topic名称", payload)
方案3:自定义MessageConverter适配字符串payload
如果业务逻辑必须传入字符串类型的payload,又不想被二次序列化,可以自定义PubSubMessageConverter,对字符串类型做特殊处理:
class CustomPubSubMessageConverter(objectMapper: ObjectMapper) : JacksonPubSubMessageConverter(objectMapper) { override fun toPubSubMessage(payload: Any, headers: MutableMap<String, Any>): PubSubMessage { // 如果payload是字符串,直接转成字节不用JSON序列化 if (payload is String) { return PubSubMessage.newBuilder() .setData(ByteString.copyFrom(payload, StandardCharsets.UTF_8)) .putAllHeaders(headers) .build() } // 其他类型走原有Jackson序列化逻辑 return super.toPubSubMessage(payload, headers) } }
把自定义的Converter注册为Spring Bean替换默认的JacksonPubSubMessageConverter即可。
内容的提问来源于stack exchange,提问作者Jonas Giedriauskas

