Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Hocon polymorphic serialization #2151

Merged
merged 5 commits into from
Dec 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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(),
)
}
}