From da5a03f4739ec10512b5b5a9fd957b59df8fc88a Mon Sep 17 00:00:00 2001 From: Matt Date: Tue, 10 Jan 2023 14:45:16 +0000 Subject: [PATCH 1/5] fix: Hocon polymorphic serialization --- .../kotlinx/serialization/hocon/Hocon.kt | 33 +++++++++++++++++-- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/Hocon.kt b/formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/Hocon.kt index f2f277948c..8320a750a4 100644 --- a/formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/Hocon.kt +++ b/formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/Hocon.kt @@ -145,7 +145,7 @@ public sealed class Hocon( } - private inner class ConfigReader(val conf: Config) : ConfigConverter() { + private inner class ConfigReader(val conf: Config, private val poly: Boolean = false) : ConfigConverter() { private var ind = -1 override fun decodeElementIndex(descriptor: SerialDescriptor): Int { @@ -161,8 +161,12 @@ public sealed class Hocon( private fun composeName(parentName: String, childName: String) = if (parentName.isEmpty()) childName else "$parentName.$childName" - override fun SerialDescriptor.getTag(index: Int): String = - composeName(currentTagOrNull.orEmpty(), getConventionElementName(index, useConfigNamingConvention)) + override fun SerialDescriptor.getTag(index: Int): String { + return if (!poly) composeName( + currentTagOrNull.orEmpty(), + getConventionElementName(index, useConfigNamingConvention) + ) else getElementName(index) + } override fun decodeNotNullMark(): Boolean { // Tag might be null for top-level deserialization @@ -206,6 +210,28 @@ public sealed class Hocon( } } + private inner class PolymorphConfigReader(private val conf: Config) : ConfigConverter() { + private var ind = -1 + + override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder = + when { + // Polymorph should always be object-like I believe? + descriptor.kind.objLike -> ConfigReader(conf, true) + else -> this + } + + override fun SerialDescriptor.getTag(index: Int): String = getElementName(index) + + override fun decodeElementIndex(descriptor: SerialDescriptor): Int { + ind++ + return if (ind >= descriptor.elementsCount) DECODE_DONE else ind + } + + override fun getValueFromTaggedConfig(tag: String, valueResolver: (Config, String) -> E): E { + return valueResolver(conf, tag) + } + } + private inner class ListConfigReader(private val list: ConfigList) : ConfigConverter() { private var ind = -1 @@ -216,6 +242,7 @@ public sealed class Hocon( override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder = when { + descriptor.kind is PolymorphicKind -> PolymorphConfigReader((list[currentTag] as ConfigObject).toConfig()) descriptor.kind.listLike -> ListConfigReader(list[currentTag] as ConfigList) descriptor.kind.objLike -> ConfigReader((list[currentTag] as ConfigObject).toConfig()) descriptor.kind == StructureKind.MAP -> MapConfigReader(list[currentTag] as ConfigObject) From b48b2ba3bac1ba7669b9c6b9d9fea64fe7d6db98 Mon Sep 17 00:00:00 2001 From: Matt Date: Tue, 10 Jan 2023 15:15:28 +0000 Subject: [PATCH 2/5] fix: Add "fix" to MapConfigReader as well --- .../hocon/src/main/kotlin/kotlinx/serialization/hocon/Hocon.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/Hocon.kt b/formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/Hocon.kt index 8320a750a4..f1986df34b 100644 --- a/formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/Hocon.kt +++ b/formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/Hocon.kt @@ -283,6 +283,7 @@ public sealed class Hocon( override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder = when { + descriptor.kind is PolymorphicKind -> PolymorphConfigReader((values[currentTag / 2] as ConfigObject).toConfig()) descriptor.kind.listLike -> ListConfigReader(values[currentTag / 2] as ConfigList) descriptor.kind.objLike -> ConfigReader((values[currentTag / 2] as ConfigObject).toConfig()) descriptor.kind == StructureKind.MAP -> MapConfigReader(values[currentTag / 2] as ConfigObject) From 7a42db99201180cce9912e19c5e5c2d995a16075 Mon Sep 17 00:00:00 2001 From: Matt Date: Sat, 18 Nov 2023 13:03:13 +0000 Subject: [PATCH 3/5] chore: Added test for the fix --- .../kotlinx/serialization/hocon/Hocon.kt | 6 ++--- .../hocon/HoconPolymorphismTest.kt | 24 +++++++++++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/Hocon.kt b/formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/Hocon.kt index f1986df34b..5758f08778 100644 --- a/formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/Hocon.kt +++ b/formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/Hocon.kt @@ -145,7 +145,7 @@ public sealed class Hocon( } - private inner class ConfigReader(val conf: Config, private val poly: Boolean = false) : ConfigConverter() { + private inner class ConfigReader(val conf: Config, private val isPolymorph: Boolean = false) : ConfigConverter() { private var ind = -1 override fun decodeElementIndex(descriptor: SerialDescriptor): Int { @@ -162,7 +162,7 @@ public sealed class Hocon( if (parentName.isEmpty()) childName else "$parentName.$childName" override fun SerialDescriptor.getTag(index: Int): String { - return if (!poly) composeName( + return if (!isPolymorph) composeName( currentTagOrNull.orEmpty(), getConventionElementName(index, useConfigNamingConvention) ) else getElementName(index) @@ -216,7 +216,7 @@ public sealed class Hocon( override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder = when { // Polymorph should always be object-like I believe? - descriptor.kind.objLike -> ConfigReader(conf, true) + descriptor.kind.objLike -> ConfigReader(conf, isPolymorph = true) else -> this } diff --git a/formats/hocon/src/test/kotlin/kotlinx/serialization/hocon/HoconPolymorphismTest.kt b/formats/hocon/src/test/kotlin/kotlinx/serialization/hocon/HoconPolymorphismTest.kt index db038e70b6..304f04b475 100644 --- a/formats/hocon/src/test/kotlin/kotlinx/serialization/hocon/HoconPolymorphismTest.kt +++ b/formats/hocon/src/test/kotlin/kotlinx/serialization/hocon/HoconPolymorphismTest.kt @@ -23,6 +23,9 @@ class HoconPolymorphismTest { data class AnnotatedTypeChild(@SerialName("my_type") val type: String) : Sealed(3) } + @Serializable + data class SealedContainer(val sealed: Collection) + @Serializable data class CompositeClass(var sealed: Sealed) @@ -102,4 +105,25 @@ class HoconPolymorphismTest { serializer = Sealed.serializer(), ) } + + @Test + fun testCollectionContainer() { + objectHocon.assertStringFormAndRestored( + expected = """ + sealed = [ + { type = annotated_type_child, my_type = override, intField = 3 } + { type = object } + { type = data_class, name = testDataClass, intField = 1 } + ] + """.trimIndent(), + original = SealedContainer( + listOf( + Sealed.AnnotatedTypeChild(type = "override"), + Sealed.ObjectChild, + Sealed.DataClassChild(name = "testDataClass"), + ) + ), + serializer = SealedContainer.serializer(), + ) + } } From 7e227edcb90397d2effdceee8623ef4231ef3986 Mon Sep 17 00:00:00 2001 From: Matt Date: Sun, 19 Nov 2023 21:05:46 +0000 Subject: [PATCH 4/5] chore: Added test for map containers --- .../hocon/HoconPolymorphismTest.kt | 30 +++++++++++++++++-- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/formats/hocon/src/test/kotlin/kotlinx/serialization/hocon/HoconPolymorphismTest.kt b/formats/hocon/src/test/kotlin/kotlinx/serialization/hocon/HoconPolymorphismTest.kt index 304f04b475..1dbc1f90a2 100644 --- a/formats/hocon/src/test/kotlin/kotlinx/serialization/hocon/HoconPolymorphismTest.kt +++ b/formats/hocon/src/test/kotlin/kotlinx/serialization/hocon/HoconPolymorphismTest.kt @@ -24,7 +24,10 @@ class HoconPolymorphismTest { } @Serializable - data class SealedContainer(val sealed: Collection) + data class SealedCollectionContainer(val sealed: Collection) + + @Serializable + data class SealedMapContainer(val sealed: Map) @Serializable data class CompositeClass(var sealed: Sealed) @@ -116,14 +119,35 @@ class HoconPolymorphismTest { { type = data_class, name = testDataClass, intField = 1 } ] """.trimIndent(), - original = SealedContainer( + original = SealedCollectionContainer( listOf( Sealed.AnnotatedTypeChild(type = "override"), Sealed.ObjectChild, Sealed.DataClassChild(name = "testDataClass"), ) ), - serializer = SealedContainer.serializer(), + serializer = SealedCollectionContainer.serializer(), + ) + } + + @Test + fun testMapContainer() { + objectHocon.assertStringFormAndRestored( + expected = """ + sealed = { + "annotated_type_child" = { type = annotated_type_child, my_type = override, intField = 3 } + "object" = { type = object } + "data_class" = { type = data_class, name = testDataClass, intField = 1 } + } + """.trimIndent(), + original = SealedMapContainer( + mapOf( + "annotated_type_child" to Sealed.AnnotatedTypeChild(type = "override"), + "object" to Sealed.ObjectChild, + "data_class" to Sealed.DataClassChild(name = "testDataClass"), + ) + ), + serializer = SealedMapContainer.serializer(), ) } } From 20fc0c1cef763f6a32b8944c8831dd5a7fcb93f0 Mon Sep 17 00:00:00 2001 From: Matt Date: Tue, 5 Dec 2023 13:17:15 +0000 Subject: [PATCH 5/5] chore: Using conventional name and renaming isPolimorphic --- .../main/kotlin/kotlinx/serialization/hocon/Hocon.kt | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/Hocon.kt b/formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/Hocon.kt index 5758f08778..5ca445ec5f 100644 --- a/formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/Hocon.kt +++ b/formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/Hocon.kt @@ -145,7 +145,7 @@ public sealed class Hocon( } - private inner class ConfigReader(val conf: Config, private val isPolymorph: Boolean = false) : ConfigConverter() { + private inner class ConfigReader(val conf: Config, private val isPolymorphic: Boolean = false) : ConfigConverter() { private var ind = -1 override fun decodeElementIndex(descriptor: SerialDescriptor): Int { @@ -162,10 +162,8 @@ public sealed class Hocon( if (parentName.isEmpty()) childName else "$parentName.$childName" override fun SerialDescriptor.getTag(index: Int): String { - return if (!isPolymorph) composeName( - currentTagOrNull.orEmpty(), - getConventionElementName(index, useConfigNamingConvention) - ) else getElementName(index) + val conventionName = getConventionElementName(index, useConfigNamingConvention) + return if (!isPolymorphic) composeName(currentTagOrNull.orEmpty(), conventionName) else conventionName } override fun decodeNotNullMark(): Boolean { @@ -215,8 +213,7 @@ public sealed class Hocon( override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder = when { - // Polymorph should always be object-like I believe? - descriptor.kind.objLike -> ConfigReader(conf, isPolymorph = true) + descriptor.kind.objLike -> ConfigReader(conf, isPolymorphic = true) else -> this }