You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin中BufferedImage列表序列化报错:找不到对应序列化器

问题描述

我正在实现一个协议,需要通过Socket发送一组小图片,采用JSON格式并将图片Base64编码。编写了如下数据类:

@Serializable
sealed class CmdBase {
    abstract val cmd: Command
}

@Serializable
@SerialName("CmdIdImgs")
class CmdIdImgs(
    override val cmd: Command,
    val id: String,
    @Serializable(with = ImageListSerializer::class)
    val thumbnails: List<BufferedImage>) : CmdBase()

随后为BufferedImage添加序列化器:

object ImageSerializer: KSerializer<BufferedImage> {
    override val descriptor = PrimitiveSerialDescriptor("Image.image", PrimitiveKind.STRING)

    override fun deserialize(decoder: Decoder): BufferedImage {
        val b64str = decoder.decodeString()
        return ImageIO.read(ByteArrayInputStream(Base64.getDecoder().decode(b64str)))
    }

    override fun serialize(encoder: Encoder, value: BufferedImage) {
        val buff = ByteArrayOutputStream()
        ImageIO.write(value, "PNG", buff)
        val b64str = Base64.getEncoder().encodeToString(buff.toByteArray())
        encoder.encodeString(b64str)
    }
}

由于是BufferedImage列表,又添加了列表序列化器:

class ImageListSerializer: KSerializer<List<BufferedImage>> {
    private val listSerializer = ListSerializer(ImageSerializer)
    override val descriptor: SerialDescriptor = listSerializer.descriptor

    override fun serialize(encoder: Encoder, value: List<BufferedImage>) {
        listSerializer.serialize(encoder, value)
    }

    override fun deserialize(decoder: Decoder): List<BufferedImage> = with(decoder as JsonDecoder) {
        decodeJsonElement().jsonArray.mapNotNull {
            try {
                json.decodeFromJsonElement(ImageSerializer, it)
            } catch (e: SerializationException) {
                e.printStackTrace()
                null
            }
        }
    }
}

最后为CmdIdImgs类添加序列化器:

object CmdIdImgsSerializer : SerializationStrategy<CmdIdImgs>, DeserializationStrategy<CmdIdImgs> {
    override val descriptor = buildClassSerialDescriptor("CmdIdImgs") {
        element("cmd", Command.serializer().descriptor)
        element("id", String.serializer().descriptor)
        element("thumbnails", ImageListSerializer().descriptor)
    }

    override fun serialize(encoder: Encoder, value: CmdIdImgs) {
        encoder.encodeStructure(descriptor) {
            encodeSerializableElement(descriptor, 0, Command.serializer(), value.cmd)
            encodeSerializableElement(descriptor, 1, String.serializer(), value.id)
            encodeSerializableElement(descriptor, 2, ImageListSerializer(), value.thumbnails)
        }
    }

    override fun deserialize(decoder: Decoder): CmdIdImgs =
        decoder.decodeStructure(descriptor) {
            var cmd: Command = Command.FULL_TREE
            var id: String = ""
            var thumbnails: List<BufferedImage> = listOf()
            loop@ while (true) {
                when (val i = decodeElementIndex(descriptor)) {
                    0 -> cmd = decodeSerializableElement(descriptor, i, Command.serializer())
                    1 -> id = decodeSerializableElement(descriptor, i, String.serializer())
                    2 -> thumbnails = decodeSerializableElement(descriptor, i, ImageListSerializer())
                    CompositeDecoder.DECODE_DONE -> break
                    else -> throw SerializationException("Unknown index $i")
                }
            }
            CmdIdImgs(cmd, id, thumbnails)
        }
}

但运行时仍报错:

Serializer has not been found for type 'BufferedImage'

报错指向CmdIdImgs类中的val thumbnails: List<BufferedImage>字段。作为Kotlin新手,请问我哪里出错了?


问题原因与修复方案

核心问题

你同时做了两件冲突的操作:

  1. 给CmdIdImgs类手动编写了自定义序列化器CmdIdImgsSerializer
  2. 又给CmdIdImgs类加上了@Serializable注解

Kotlin Serialization的规则是:当类同时存在@Serializable注解和手动序列化器时,会优先尝试用注解生成默认序列化逻辑,但默认逻辑无法识别BufferedImage这种自定义类型,所以抛出找不到序列化器的错误。另外,你给thumbnails字段标注的@Serializable(with = ImageListSerializer::class)在使用手动序列化器时不会生效,因为手动序列化器会接管整个类的序列化流程。

修复步骤

方案一:移除手动编写的CmdIdImgsSerializer(推荐)

既然已经给字段指定了自定义序列化器,完全不需要手动编写整个类的序列化器,框架会自动处理:

  1. 删除CmdIdImgsSerializer的全部代码
  2. 保留CmdIdImgs类上的@Serializable和@SerialName("CmdIdImgs")注解
  3. 保留thumbnails字段的@Serializable(with = ImageListSerializer::class)注解

修改后的CmdIdImgs类:

@Serializable
@SerialName("CmdIdImgs")
class CmdIdImgs(
    override val cmd: Command,
    val id: String,
    @Serializable(with = ImageListSerializer::class)
    val thumbnails: List<BufferedImage>) : CmdBase()

方案二:保留手动序列化器,移除类的@Serializable注解

如果一定要用手动编写的序列化器,需要:

  1. 去掉CmdIdImgs类上的@Serializable注解
  2. 在序列化/反序列化时显式指定CmdIdImgsSerializer:
// 序列化示例
val jsonString = Json.encodeToString(CmdIdImgsSerializer, yourCmdIdImgsInstance)

// 反序列化示例
val cmdIdImgs = Json.decodeFromString(CmdIdImgsSerializer, jsonString)

额外优化建议

你的ImageListSerializer其实没必要手动编写,直接用ListSerializer(ImageSerializer)即可简化代码:

  • 删除ImageListSerializer类
  • 将thumbnails字段的注解改为:
@Serializable(with = ListSerializer::class)
val thumbnails: List<@Serializable(with = ImageSerializer::class) BufferedImage>

或者更简洁的方式:提前给BufferedImage注册全局默认序列化器,创建Json实例时配置:

val json = Json {
    serializersModule = SerializersModule {
        contextual(BufferedImage::class, ImageSerializer)
    }
}

这样List<BufferedImage>就不需要额外指定序列化器,直接使用即可。


内容的提问来源于stack exchange,提问作者Kingfranz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.23 12:36:32