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