Last active
March 24, 2022 21:58
-
-
Save ephemient/65a3ee700f020661858c89b5648a2772 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.os.Parcel | |
import kotlinx.parcelize.Parceler | |
import kotlinx.serialization.BinaryFormat | |
import kotlinx.serialization.ExperimentalSerializationApi | |
import kotlinx.serialization.KSerializer | |
/** | |
* This class allows any [kotlinx.serialization.Serializable] type to be easily used within a | |
* [kotlinx.parcelize.Parcelize] type without implementing [android.os.Parcelable]. | |
* | |
* For example, given | |
* ```kotlin | |
* @Serializable | |
* data class Foo(...) // not Parcelable | |
* ``` | |
* a [Parceler] is all that is needed to make it usable in | |
* ```kotlin | |
* @Parcelize | |
* @TypeParceler<Foo, FooParceler> | |
* data class Bar(val foo: @WriteWith<FooParceler> Foo) | |
* | |
* internal object FooParceler : SerializationParceler<Foo> { | |
* override val serializer = Foo.serializer() | |
* } | |
* ``` | |
* where only one of {[kotlinx.parcelize.TypeParceler], [kotlinx.parcelize.WriteWith]} is needed. | |
*/ | |
abstract class SerializationParceler<T> : Parceler<T> { | |
abstract val serializer: KSerializer<T> | |
open val binaryFormat: BinaryFormat | |
get() = simpleBinaryFormat | |
override fun T.write(parcel: Parcel, flags: Int) { | |
val bytes = binaryFormat.encodeToByteArray(serializer, this) | |
parcel.writeInt(bytes.size) | |
parcel.writeByteArray(bytes) | |
} | |
override fun create(parcel: Parcel): T { | |
val bytes = ByteArray(parcel.readInt()) | |
parcel.readByteArray(bytes) | |
return binaryFormat.decodeFromByteArray(serializer, bytes) | |
} | |
companion object { | |
@OptIn(ExperimentalSerializationApi::class) | |
private val simpleBinaryFormat: BinaryFormat = SimpleBinaryFormat() | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.io.ByteArrayInputStream | |
import java.io.ByteArrayOutputStream | |
import java.io.DataInput | |
import java.io.DataInputStream | |
import java.io.DataOutput | |
import java.io.DataOutputStream | |
import kotlinx.serialization.BinaryFormat | |
import kotlinx.serialization.DeserializationStrategy | |
import kotlinx.serialization.ExperimentalSerializationApi | |
import kotlinx.serialization.SerializationException | |
import kotlinx.serialization.SerializationStrategy | |
import kotlinx.serialization.descriptors.SerialDescriptor | |
import kotlinx.serialization.encoding.AbstractDecoder | |
import kotlinx.serialization.encoding.AbstractEncoder | |
import kotlinx.serialization.encoding.CompositeDecoder.Companion.DECODE_DONE | |
import kotlinx.serialization.modules.EmptySerializersModule | |
import kotlinx.serialization.modules.SerializersModule | |
@ExperimentalSerializationApi | |
class SimpleBinaryFormat constructor( | |
override val serializersModule: SerializersModule = EmptySerializersModule, | |
) : BinaryFormat { | |
override fun <T> encodeToByteArray(serializer: SerializationStrategy<T>, value: T): ByteArray = | |
ByteArrayOutputStream().apply { | |
DataOutputStream(this).use { encodeTo(serializer, value, it) } | |
}.toByteArray() | |
fun <T> encodeTo(serializer: SerializationStrategy<T>, value: T, output: DataOutput) { | |
BinaryEncoder(output).encodeSerializableValue(serializer, value) | |
} | |
override fun <T> decodeFromByteArray(deserializer: DeserializationStrategy<T>, bytes: ByteArray): T = | |
with(ByteArrayInputStream(bytes)) { | |
DataInputStream(this).use { decodeFrom(deserializer, it) }.also { check(available() == 0) } | |
} | |
fun <T> decodeFrom(deserializer: DeserializationStrategy<T>, input: DataInput): T = | |
BinaryDecoder(input).decodeSerializableValue(deserializer) | |
private inner class BinaryEncoder(private val output: DataOutput) : AbstractEncoder() { | |
override val serializersModule: SerializersModule | |
get() = [email protected] | |
override fun endStructure(descriptor: SerialDescriptor) = output.writeInt(DECODE_DONE) | |
override fun encodeElement(descriptor: SerialDescriptor, index: Int): Boolean = | |
output.writeInt(index).let { true } | |
override fun encodeNull() = encodeBoolean(false) | |
override fun encodeNotNullMark() = encodeBoolean(true) | |
override fun encodeBoolean(value: Boolean) = output.writeBoolean(value) | |
override fun encodeByte(value: Byte) = output.writeByte(value.toInt()) | |
override fun encodeShort(value: Short) = output.writeShort(value.toInt()) | |
override fun encodeInt(value: Int) = output.writeInt(value) | |
override fun encodeLong(value: Long) = output.writeLong(value) | |
override fun encodeFloat(value: Float) = output.writeFloat(value) | |
override fun encodeDouble(value: Double) = output.writeDouble(value) | |
override fun encodeChar(value: Char) = output.writeChar(value.code) | |
override fun encodeString(value: String) = output.writeUTF(value) | |
override fun encodeEnum(enumDescriptor: SerialDescriptor, index: Int) = encodeInt(index) | |
} | |
private inner class BinaryDecoder(private val input: DataInput) : AbstractDecoder() { | |
private var isEnded = false | |
override val serializersModule: SerializersModule | |
get() = [email protected] | |
override fun endStructure(descriptor: SerialDescriptor) { | |
val index = if (isEnded) DECODE_DONE else input.readInt() | |
if (index != DECODE_DONE) throw SerializationException("Unexpected index: $index") | |
isEnded = false | |
} | |
override fun decodeElementIndex(descriptor: SerialDescriptor): Int = | |
input.readInt().also { isEnded = it == DECODE_DONE } | |
override fun decodeNotNullMark(): Boolean = decodeBoolean() | |
override fun decodeBoolean(): Boolean = input.readBoolean() | |
override fun decodeByte(): Byte = input.readByte() | |
override fun decodeShort(): Short = input.readShort() | |
override fun decodeInt(): Int = input.readInt() | |
override fun decodeLong(): Long = input.readLong() | |
override fun decodeFloat(): Float = input.readFloat() | |
override fun decodeDouble(): Double = input.readDouble() | |
override fun decodeChar(): Char = input.readChar() | |
override fun decodeString(): String = input.readUTF() | |
override fun decodeEnum(enumDescriptor: SerialDescriptor): Int = decodeInt() | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@file:OptIn(ExperimentalSerializationApi::class) | |
import kotlinx.serialization.ExperimentalSerializationApi | |
import kotlinx.serialization.KSerializer | |
import kotlinx.serialization.Polymorphic | |
import kotlinx.serialization.Serializable | |
import kotlinx.serialization.builtins.ListSerializer | |
import kotlinx.serialization.builtins.MapSerializer | |
import kotlinx.serialization.builtins.nullable | |
import kotlinx.serialization.builtins.serializer | |
import kotlinx.serialization.modules.SerializersModule | |
import kotlinx.serialization.modules.polymorphic | |
import kotlinx.serialization.modules.subclass | |
import org.junit.Assert.assertEquals | |
import org.junit.Test | |
class SimpleBinaryFormatTest { | |
private val format = SimpleBinaryFormat( | |
SerializersModule { | |
polymorphic(Interface::class) { | |
subclass(Simple.serializer()) | |
subclass(Singleton.serializer()) | |
} | |
} | |
) | |
@Test | |
fun builtin() { | |
testRoundTrip(Boolean.serializer(), true) | |
testRoundTrip(Byte.serializer(), -59) | |
testRoundTrip(Short.serializer(), 2600) | |
testRoundTrip(Int.serializer(), 2137353318) | |
testRoundTrip(Long.serializer(), -4982089500409860083) | |
testRoundTrip(Float.serializer(), kotlin.math.E.toFloat()) | |
testRoundTrip(Double.serializer(), kotlin.math.PI) | |
testRoundTrip(Char.serializer(), '※') | |
testRoundTrip(String.serializer(), "Hello, world!") | |
} | |
@Test | |
fun simple() { | |
testRoundTrip(Simple.serializer(), Simple(42, "forty-two")) | |
testRoundTrip(Simple.serializer(), Simple(60)) | |
} | |
@Test | |
fun generic() { | |
testRoundTrip(Holder.serializer(Simple.serializer()), Holder(Simple(42, "forty-two"))) | |
} | |
@Test | |
fun nullable() { | |
testRoundTrip(Simple.serializer().nullable, Simple(0)) | |
testRoundTrip(Simple.serializer().nullable, null) | |
testRoundTrip(Nullable.serializer(), Nullable(Simple(0))) | |
testRoundTrip(Nullable.serializer(), Nullable(null)) | |
} | |
@Test | |
fun collection() { | |
val list = listOf(Simple(1), Simple(2), Simple(3)) | |
val map = mapOf("a" to Simple(4), "b" to Simple(5), "c" to Simple(6)) | |
testRoundTrip(ListSerializer(Simple.serializer()), list) | |
testRoundTrip(MapSerializer(String.serializer(), Simple.serializer()), map) | |
testRoundTrip(Container.serializer(), Container(list, map)) | |
} | |
@Test | |
fun singleton() { | |
testRoundTrip(Singleton.serializer(), Singleton) | |
} | |
@Test | |
fun polymorphic() { | |
testRoundTrip(Wrapper.serializer(), Wrapper(Simple(42, "forty-two"), Sealed.Data(100))) | |
testRoundTrip(Wrapper.serializer(), Wrapper(Singleton, Sealed.Object)) | |
} | |
private fun <T> testRoundTrip(serializer: KSerializer<T>, data: T) { | |
val bytes = format.encodeToByteArray(serializer, data) | |
println("$data -> " + bytes.joinToString(" ") { "%02X".format(it) }) | |
assertEquals(data, format.decodeFromByteArray(serializer, bytes)) | |
} | |
} | |
private interface Interface | |
@Serializable | |
private data class Simple(val first: Int, val second: String = "second") : Interface | |
@Serializable | |
private data class Holder<T>(val value: T) | |
@Serializable | |
private data class Nullable(val nullable: Simple?) : Interface | |
@Serializable | |
private data class Container(val list: List<Simple>, val map: Map<String, Simple>) : Interface | |
@Serializable | |
private object Singleton : Interface | |
@Serializable | |
sealed class Sealed { | |
@Serializable | |
data class Data(val id: Int) : Sealed() | |
@Serializable | |
object Object : Sealed() | |
} | |
@Serializable | |
private data class Wrapper(@Polymorphic val open: Interface, val closed: Sealed) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment