diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/encoders/TomlAbstractEncoder.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/encoders/TomlAbstractEncoder.kt index fedcf55..61a3584 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/encoders/TomlAbstractEncoder.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/encoders/TomlAbstractEncoder.kt @@ -15,6 +15,7 @@ import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.SerializationStrategy import kotlinx.serialization.descriptors.* import kotlinx.serialization.encoding.AbstractEncoder +import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.modules.SerializersModule /** @@ -104,6 +105,12 @@ public abstract class TomlAbstractEncoder protected constructor( appendValue(TomlNull()) } + override fun encodeInline(descriptor: SerialDescriptor): Encoder { + // Value (inline) class always has one element + encodeElement(descriptor, 0) + return this + } + override fun encodeString(value: String) { if (!encodeAsKey(value)) { appendValue( @@ -116,6 +123,7 @@ public abstract class TomlAbstractEncoder protected constructor( } } + @Suppress("NESTED_BLOCK") override fun encodeSerializableValue(serializer: SerializationStrategy, value: T) { when (val desc = serializer.descriptor) { instantDescriptor, @@ -126,14 +134,14 @@ public abstract class TomlAbstractEncoder protected constructor( } else -> when (val kind = desc.kind) { is StructureKind, - is PolymorphicKind -> if (!encodeAsKey(value as Any, desc.serialName)) { - val encoder = encodeStructure(kind) - - serializer.serialize(encoder, value) - - elementIndex = encoder.elementIndex - - attributes.reset() + is PolymorphicKind -> when { + desc.isInline -> serializer.serialize(this, value) + !encodeAsKey(value as Any, desc.serialName) -> { + val encoder = encodeStructure(kind) + serializer.serialize(encoder, value) + elementIndex = encoder.elementIndex + attributes.reset() + } } else -> super.encodeSerializableValue(serializer, value) } @@ -204,7 +212,13 @@ public abstract class TomlAbstractEncoder protected constructor( protected open fun isNextElementKey(descriptor: SerialDescriptor, index: Int): Boolean { when (val kind = descriptor.kind) { - StructureKind.CLASS -> setKey(descriptor.getElementName(index)) + StructureKind.CLASS -> { + // We should keep previous key when we have value (inline) class + // But if key is null, it means that value class isn't nested, and we have to use its own key + if (!descriptor.isInline || attributes.key == null) { + setKey(descriptor.getElementName(index)) + } + } StructureKind.MAP -> { // When the index is even (key) mark the next element as a key and // skip annotations and element index incrementing. diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/ValueClassEncoderTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/ValueClassEncoderTest.kt new file mode 100644 index 0000000..07d03ab --- /dev/null +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/ValueClassEncoderTest.kt @@ -0,0 +1,159 @@ +package com.akuleshov7.ktoml.encoders + +import com.akuleshov7.ktoml.Toml +import kotlinx.serialization.Serializable +import kotlinx.serialization.encodeToString +import kotlin.jvm.JvmInline +import kotlin.test.Test +import kotlin.test.assertEquals + +class ValueClassEncoderTest { + + @Serializable + @JvmInline + value class Color(val rgb: Int) + + @Serializable + data class NamedColor(val color: Color, val name: String) + + @Test + fun testForSimpleValueClass() { + val color = Color(15) + val result = Toml.encodeToString(color) + + assertEquals("rgb = 15", result) + } + + @Test + fun testForNestedValueClass() { + val namedColor = NamedColor( + Color(150), + "black" + ) + + val result = Toml.encodeToString(namedColor) + assertEquals( + """ + color = 150 + name = "black" + """.trimIndent(), + result + ) + } + + @Test + fun testForLisOfValueClass() { + @Serializable + class Palette(val colors: List) + val palette = Palette( + listOf( + Color(0), + Color(255), + Color(128), + ) + ) + + val result = Toml.encodeToString(palette) + assertEquals("colors = [ 0, 255, 128 ]", result) + } + + @Serializable + @JvmInline + value class Num(val int: Int) + + @Serializable + data class Nums( + val num1: Num, + val num2: Num, + ) + + @Test + fun testForMultipleValueClass() { + val nums = Nums( + num1 = Num(5), + num2 = Num(111) + ) + val result = Toml.encodeToString(nums) + + assertEquals( + """ + num1 = 5 + num2 = 111 + """.trimIndent(), + result + ) + } + + @Serializable + data class MyObject( + val height: Int, + val width: Int + ) + + @Serializable + @JvmInline + value class Info(val obj: MyObject) + + @Serializable + data class InfoWrapper( + val metaInfo1: Int, + val info: Info, + val metaInfo2: String + ) + + @Test + fun testForValueClassWithObjectInside() { + val obj = InfoWrapper( + metaInfo1 = 1, + info = Info(MyObject(10, 20)), + metaInfo2 = "test" + ) + val result = Toml.encodeToString(obj) + + assertEquals( + """ + metaInfo1 = 1 + metaInfo2 = "test" + + [info] + height = 10 + width = 20 + """.trimIndent(), + result + ) + } + + @Serializable + @JvmInline + value class AnotherInfoWrapper(val info: Info) + + @Test + fun testForValueClassInsideValueClass() { + val obj = AnotherInfoWrapper(Info(MyObject(10, 20))) + val result = Toml.encodeToString(obj) + + assertEquals( + """ + [info] + height = 10 + width = 20 + """.trimIndent(), + result + ) + } + + @Test + fun testDataClassInsideValueClass() { + val obj = Info(MyObject(32, 64)) + val result = Toml.encodeToString(obj) + + assertEquals( + """ + [obj] + height = 32 + width = 64 + """.trimIndent(), + result + ) + } +}