Home > database >  How do you serialize a list of BufferedImage in Kotlin?
How do you serialize a list of BufferedImage in Kotlin?

Time:08-07

I'm trying to implement a protocol where (part of it) is sending a list of small images over a socket. I'm using JSON and the images are base64 encoded.

Here's the data classes

@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()

So I added a serializer for 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)
    }
}

But it's a list of BufferedImages, so I added a serializer for that

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
            }
        }
    }
}

And now a serializer for the whole class

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

But something is wrong, because I still get

Serializer has not been found for type 'BufferedImage'

on the 'val thumbnails: List<BufferedImage>' in the CmdIdImgs class

Any idea what I'm doing wrong?

Probably a lot since I'm a newbie with Kotlin :-)

CodePudding user response:

Since you want to send JSON to your socket, I recommend you use de facto Jackson. If that's ok for you, then this is simpler - you only need to create one specialised serializer. Here's working code (deserializer TODO).

import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.databind.SerializerProvider
import com.fasterxml.jackson.databind.json.JsonMapper
import com.fasterxml.jackson.databind.module.SimpleModule
import com.fasterxml.jackson.databind.ser.std.StdSerializer
import com.fasterxml.jackson.module.kotlin.KotlinModule
import java.awt.image.BufferedImage
import java.io.ByteArrayOutputStream
import java.io.File
import java.util.*
import javax.imageio.ImageIO

sealed class CmdBase {
    abstract val cmd: String // Command
}

class CmdIdImgs(
    override val cmd: String, // Command
    val id: String,
    val thumbnails: List<BufferedImage>,
) : CmdBase()

class BufferedImageSerializer : StdSerializer<BufferedImage>(BufferedImage::class.java) {
    override fun serialize(value: BufferedImage?, jgen: JsonGenerator, provider: SerializerProvider?) {
        value?.let {
            val buff = ByteArrayOutputStream()
            ImageIO.write(it, "PNG", buff)
            val b64str = Base64.getEncoder().encodeToString(buff.toByteArray())
            jgen.writeString(b64str)
        }
    }
}

//class BufferedImageDeserializer : StdDeserializer<BufferedImage>(BufferedImage::class.java) {
//    override fun deserialize(jp: JsonParser, ctxt: DeserializationContext?): BufferedImage? {
//        val node: JsonNode = jp.codec.readTree(jp)
//        if (!node.isTextual) {
//            node.asText()....
//        }
//    }
//}

val IMAGE_MODULE = SimpleModule().apply {
    this.addSerializer(BufferedImage::class.java, BufferedImageSerializer())
    //this.addDeserializer(BufferedImage::class.java, BufferedImageDeserializer())
}

val MAPPER = JsonMapper.builder()
    .addModule(KotlinModule(strictNullChecks = true))
    .addModule(IMAGE_MODULE)
    .build()

fun main(args: Array<String>) {
    val cmdIdImgs = CmdIdImgs("x", "1", listOf(ImageIO.read(File("/tmp/image.png"))))
    println(MAPPER.writeValueAsString(cmdIdImgs))
}

Prints

{"cmd":"x","id":"1","thumbnails":["iVBORw0KGgoAAAAN....
  • Related