Skip to content

Commit

Permalink
kotlinx.serialization support (experimental)
Browse files Browse the repository at this point in the history
  • Loading branch information
belyaev-mikhail committed Jul 27, 2021
1 parent 13f2a21 commit 901d0b5
Show file tree
Hide file tree
Showing 5 changed files with 198 additions and 3 deletions.
11 changes: 8 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ buildscript {
}

plugins {
id("org.jetbrains.kotlin.multiplatform").version("1.5.0")
id("org.jetbrains.kotlin.multiplatform").version("1.5.21")
id("org.jetbrains.kotlin.plugin.serialization").version("1.5.21")
id "maven-publish"
}

group 'ru.spbstu'
version (findProperty('forceVersion') ?: '0.0.2.3')
version (findProperty('forceVersion') ?: '0.0.2.4')

repositories {
mavenCentral()
Expand Down Expand Up @@ -70,7 +71,7 @@ kotlin {
}
}
}
js {
js(IR) {
nodejs()
browser {
testTask {
Expand All @@ -97,12 +98,16 @@ kotlin {
}

commonMain {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:1.2.2")
}
}
commonTest {
dependsOn(commonMain)
dependencies {
implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common"))
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.2")
}
}
jvmMain {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
@file:Suppress("UNUSED_PARAMETER", "NOTHING_TO_INLINE")
@file:OptIn(ExperimentalSerializationApi::class)
package ru.spbstu.ktuples;

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

<%
def k = number
%>

class TupleDescriptor(vararg val elementDescriptors: SerialDescriptor) : SerialDescriptor {
private val size get() = elementDescriptors.size
override val kind: SerialKind get() = StructureKind.LIST
override val elementsCount: Int get() = size

@ExperimentalSerializationApi
override val serialName: String = "ru.spbstu.ktuples.Tuple${'$'}size"

private val indexRange get() = 0 until elementsCount
private fun requireValidIndex(index: Int) {
require(index in indexRange) { "Illegal index ${'$'}index, ${'$'}serialName expects only indices in range ${'$'}indexRange"}
}

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 {
requireValidIndex(index)
return false
}

override fun getElementAnnotations(index: Int): List<Annotation> {
requireValidIndex(index)
return emptyList()
}

override fun getElementDescriptor(index: Int): SerialDescriptor {
requireValidIndex(index)
return elementDescriptors[index]
}

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is TupleDescriptor) return false
if (elementDescriptors.contentEquals(other.elementDescriptors)
&& serialName == other.serialName) return true
return false
}

override fun hashCode(): Int {
return elementDescriptors.contentHashCode() * 31 + serialName.hashCode()
}

override fun toString(): String =
serialName + elementDescriptors.joinToString(prefix = "(", postfix = ")")
}

object Tuple0Serializer: KSerializer<Tuple0> {
override val descriptor: SerialDescriptor = TupleDescriptor()

override fun serialize(encoder: Encoder, value: Tuple0) {
val composite = encoder.beginCollection(descriptor, 0)
composite.endStructure(descriptor)
}

override fun deserialize(decoder: Decoder): Tuple0 {
val composite = decoder.beginStructure(descriptor)
if (!composite.decodeSequentially()) {
val index = composite.decodeElementIndex(descriptor)
require(index == CompositeDecoder.DECODE_DONE)
}
composite.endStructure(descriptor)
return Tuple0
/* do nothing*/
}
}


<% (k-1).times { m ->
def i = m + 1

def types = i.join{ "T$it" }
def typesOut = i.join{ "out T$it" }
def typesComparable = i.join{ "T$it: Comparable<T$it>" }

def stringTemplate = i.join{ "\$v$it" }
def params = i.join{ "val v$it: T$it" }
def serializers = i.join { "val v${it}Serializer: KSerializer<T${it}>" }
def nnvalues = i.join{ "v${it}!!" }
def descriptors = i.join{ "v${it}Serializer.descriptor" }
%>

class Tuple${i}Serializer<${types}>(${serializers}):
KSerializer<Tuple${i}<${types}>> {
override val descriptor: SerialDescriptor = TupleDescriptor(${descriptors})

override fun serialize(encoder: Encoder, value: Tuple${i}<${types}>) {
val composite = encoder.beginCollection(descriptor, ${i})
<% i.times { j -> %>
composite.encodeSerializableElement(descriptor, ${j}, v${j}Serializer, value.v${j})
<% } // i.times %>
composite.endStructure(descriptor)
}

override fun deserialize(decoder: Decoder): Tuple${i}<${types}> {
val composite = decoder.beginStructure(descriptor)
<% i.times { j -> %>
var v${j}: T${j}? = null
<% } // i.times %>

if (composite.decodeSequentially()) {
<% i.times { j -> %>
v${j} = composite.decodeSerializableElement(descriptor, ${j}, v${j}Serializer, v${j})
<% } // i.times %>
} else {
while (true) {
val index = composite.decodeElementIndex(descriptor)
if (index == CompositeDecoder.DECODE_DONE) break
when (index) {
<% i.times { j -> %>
${j} -> v${j} = composite.decodeSerializableElement(descriptor, ${j}, v${j}Serializer, v${j})
<% } // i.times %>
else -> throw IllegalArgumentException()
}
}
}
composite.endStructure(descriptor)
<% i.times { j -> %>
check(v${j} !== null)
<% } // i.times %>
return Tuple($nnvalues)
}
}

<% } /* (1..(k-1)).each { m */ %>
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
@file:Suppress("UNUSED_PARAMETER", "NOTHING_TO_INLINE")
//@file:OptIn(ExperimentalSerializationApi::class)
package ru.spbstu.ktuples;

import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable

<%
def k = number
%>
Expand All @@ -17,6 +22,7 @@ sealed class Tuple {
/**
* A 0-tuple, mostly exists for convenience
*/
@Serializable(Tuple0Serializer::class)
object Tuple0: Tuple() {
override val size = 0
override fun toString() = "Tuple()"
Expand Down Expand Up @@ -62,6 +68,7 @@ fun Tuple0.joinToString(
* @param T$j the type of v$j
<% } // i.times %> *
*/
@Serializable(with = Tuple${i}Serializer::class)
data class Tuple$i<$typesOut>($params): Tuple() {
override val size get() = $i
override fun toString() = "Tuple($stringTemplate)"
Expand Down
27 changes: 27 additions & 0 deletions src/commonTest/kotlin/ru/spbstu/ktuples/TupleSerializersTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package ru.spbstu.ktuples

import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.encodeToJsonElement
import kotlin.test.Test
import kotlin.test.assertEquals

class TupleSerializersTest {
@Serializable
data class Moo(val t: Tuple2<String, Double>)

@Test
fun baseTest() {
val zz = Json.encodeToJsonElement(Tuple(1, "Hello", listOf(1, 2, 3)))
assertEquals(Json.parseToJsonElement("""[1, "Hello", [1, 2, 3]]"""), zz)
}

@Test
fun wrappedTest () {
val zz = Json.encodeToJsonElement(Moo(Tuple("h", 3.15)))
assertEquals(Json.parseToJsonElement("""{ "t" : ["h", 3.15] }"""), zz)
assertEquals(Moo(Tuple("h", 3.15)),
Json.decodeFromString("""{ "t" : ["h", 3.15] }"""))
}
}
13 changes: 13 additions & 0 deletions src/commonTest/kotlin/ru/spbstu/ktuples/TupleTest.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
package ru.spbstu.ktuples

import kotlinx.serialization.*
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.encodeStructure
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.Json.Default.decodeFromJsonElement
import kotlinx.serialization.json.Json.Default.decodeFromString
import kotlinx.serialization.json.encodeToJsonElement
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
Expand All @@ -8,6 +20,7 @@ import kotlin.test.assertTrue

class TupleTest {


@Test
fun `sanity_check`() {
val tup2 = Tuple(2, "Hello")
Expand Down

0 comments on commit 901d0b5

Please sign in to comment.