commonMain.com.caesarealabs.rpc4k.runtime.implementation.serializers.TupleSerializers.kt Maven / Gradle / Ivy
The newest version!
@file:OptIn(ExperimentalSerializationApi::class)
@file:Suppress("UNCHECKED_CAST", "FunctionName")
package com.caesarealabs.rpc4k.runtime.implementation.serializers
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.KSerializer
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.descriptors.SerialKind
import kotlinx.serialization.descriptors.StructureKind
import kotlinx.serialization.encoding.CompositeDecoder
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.encoding.encodeCollection
/**
* Serializes a pair as an array of two elements
*/
public fun TuplePairSerializer(keySerializer: KSerializer, valueSerializer: KSerializer): KSerializer> = BuiltinTupleSerializer(
listOf(keySerializer, valueSerializer), { listOf(it.first, it.second) }, { it[0] as K to it[1] as V }
)
/**
* Serializes a triple as an array of three elements
*/
public fun TupleTripleSerializer(
firstSerializer: KSerializer, secondSerializer: KSerializer, thirdSerializer: KSerializer
): KSerializer> = BuiltinTupleSerializer(
listOf(firstSerializer, secondSerializer, thirdSerializer),
{ listOf(it.first, it.second, it.third) },
{ Triple(it[0] as T1, it[1] as T2, it[2] as T3) }
)
/**
* Serializes a map entry the same way as a pair - array of two elements
*/
public fun TupleMapEntrySerializer(keySerializer: KSerializer, valueSerializer: KSerializer): KSerializer> = BuiltinTupleSerializer(
listOf(keySerializer, valueSerializer), { listOf(it.key, it.value) }, { MapEntryImpl(it[0] as K, it[1] as V) }
)
private class MapEntryImpl(override val key: K, override val value: V) : Map.Entry {
override fun equals(other: Any?): Boolean {
return other is Map.Entry<*, *> && other.key == key && other.value == value
}
override fun toString(): String {
return "$key=$value"
}
override fun hashCode(): Int {
var result = key?.hashCode() ?: 0
result = 31 * result + (value?.hashCode() ?: 0)
return result
}
}
/**
* Serializes tuples as an array
*/
private class BuiltinTupleSerializer(elementSerializers: List>, private val toList: (T) -> List<*>, private val fromList: (List<*>) -> T) :
KSerializer {
private val length = elementSerializers.size
private val delegate = TupleSerializer(elementSerializers)
override val descriptor: SerialDescriptor = delegate.descriptor
override fun deserialize(decoder: Decoder): T {
val list = delegate.deserialize(decoder)
require(list.size == length) {
"Expected a tuple of exactly $length elements, got ${list.size} elements"
}
return fromList(list)
}
override fun serialize(encoder: Encoder, value: T) {
delegate.serialize(encoder, toList(value))
}
}
/**
* Adapted from [kotlinx.serialization.internal.CollectionLikeSerializer], [kotlinx.serialization.internal.CollectionSerializer]
* and [kotlinx.serialization.internal.ArrayListSerializer]
*/
public class TupleSerializer(private val elementSerializers: List>) : KSerializer> {
override val descriptor: SerialDescriptor = TupleDescriptor(elementSerializers.map { it.descriptor })
override fun deserialize(decoder: Decoder): List<*> {
val builder = arrayListOf()
val startIndex = 0
val compositeDecoder = decoder.beginStructure(descriptor)
if (compositeDecoder.decodeSequentially()) {
readAll(compositeDecoder, builder, readSize(compositeDecoder, builder))
} else {
while (true) {
val index = compositeDecoder.decodeElementIndex(descriptor)
if (index == CompositeDecoder.DECODE_DONE) break
readElement(compositeDecoder, startIndex + index, builder)
}
}
compositeDecoder.endStructure(descriptor)
return builder
}
private fun readAll(decoder: CompositeDecoder, list: ArrayList, size: Int) {
require(size >= 0) { "Size must be known in advance when using READ_ALL" }
for (index in 0 until size) {
readElement(decoder, index, list)
}
}
private fun readElement(decoder: CompositeDecoder, index: Int, builder: ArrayList) {
builder.add(index, decoder.decodeSerializableElement(descriptor, index, elementSerializers[index]))
}
override fun serialize(encoder: Encoder, value: List<*>) {
val size = value.size
encoder.encodeCollection(descriptor, size) {
value.forEachIndexed { i, item ->
encodeSerializableElement(descriptor, i, elementSerializers[i] as KSerializer, item)
}
}
}
private fun readSize(decoder: CompositeDecoder, builder: ArrayList): Int {
val size = decoder.decodeCollectionSize(descriptor)
builder.ensureCapacity(size)
return size
}
}
/**
* Adapted from [kotlinx.serialization.internal.ListLikeDescriptor]
*/
internal class TupleDescriptor(val elementDescriptors: List) : SerialDescriptor {
override val kind: SerialKind get() = StructureKind.LIST
override val elementsCount: Int = elementDescriptors.size
override val serialName = "Tuple"
override fun getElementName(index: Int): String = index.toString()
override fun getElementIndex(name: String): Int =
name.toIntOrNull() ?: throw IllegalArgumentException("$name is not a valid list index")
override fun isElementOptional(index: Int): Boolean {
validateIndex(index)
return false
}
override fun getElementAnnotations(index: Int): List {
validateIndex(index)
return emptyList()
}
override fun getElementDescriptor(index: Int): SerialDescriptor {
validateIndex(index)
return elementDescriptors[index]
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is TupleDescriptor) return false
if (elementDescriptors == other.elementDescriptors && serialName == other.serialName) return true
return false
}
override fun hashCode(): Int {
return elementDescriptors.hashCode() * 31 + serialName.hashCode()
}
override fun toString(): String = "$serialName(${elementDescriptors.joinToString()})"
private fun validateIndex(index: Int) = require(index in elementDescriptors.indices) {
"Illegal index $index, $serialName expects only indices in the range ${elementDescriptors.indices}"
}
}