Skip to content

Commit

Permalink
Fix: Hocon polymorphic serialization (#2151)
Browse files Browse the repository at this point in the history
Fixes #1581
  • Loading branch information
LichtHund authored Dec 5, 2023
1 parent b994572 commit d8b98b5
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 3 deletions.
31 changes: 28 additions & 3 deletions formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/Hocon.kt
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ public sealed class Hocon(

}

private inner class ConfigReader(val conf: Config) : ConfigConverter<String>() {
private inner class ConfigReader(val conf: Config, private val isPolymorphic: Boolean = false) : ConfigConverter<String>() {
private var ind = -1

override fun decodeElementIndex(descriptor: SerialDescriptor): Int {
Expand All @@ -161,8 +161,10 @@ 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 {
val conventionName = getConventionElementName(index, useConfigNamingConvention)
return if (!isPolymorphic) composeName(currentTagOrNull.orEmpty(), conventionName) else conventionName
}

override fun decodeNotNullMark(): Boolean {
// Tag might be null for top-level deserialization
Expand Down Expand Up @@ -206,6 +208,27 @@ public sealed class Hocon(
}
}

private inner class PolymorphConfigReader(private val conf: Config) : ConfigConverter<String>() {
private var ind = -1

override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder =
when {
descriptor.kind.objLike -> ConfigReader(conf, isPolymorphic = 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 <E> getValueFromTaggedConfig(tag: String, valueResolver: (Config, String) -> E): E {
return valueResolver(conf, tag)
}
}

private inner class ListConfigReader(private val list: ConfigList) : ConfigConverter<Int>() {
private var ind = -1

Expand All @@ -216,6 +239,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)
Expand Down Expand Up @@ -256,6 +280,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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ class HoconPolymorphismTest {
data class AnnotatedTypeChild(@SerialName("my_type") val type: String) : Sealed(3)
}

@Serializable
data class SealedCollectionContainer(val sealed: Collection<Sealed>)

@Serializable
data class SealedMapContainer(val sealed: Map<String, Sealed>)

@Serializable
data class CompositeClass(var sealed: Sealed)

Expand Down Expand Up @@ -102,4 +108,46 @@ 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 = SealedCollectionContainer(
listOf(
Sealed.AnnotatedTypeChild(type = "override"),
Sealed.ObjectChild,
Sealed.DataClassChild(name = "testDataClass"),
)
),
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(),
)
}
}

0 comments on commit d8b98b5

Please sign in to comment.