Skip to content

Commit d8b98b5

Browse files
authored
Fix: Hocon polymorphic serialization (#2151)
Fixes #1581
1 parent b994572 commit d8b98b5

File tree

2 files changed

+76
-3
lines changed

2 files changed

+76
-3
lines changed

formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/Hocon.kt

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ public sealed class Hocon(
145145

146146
}
147147

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

151151
override fun decodeElementIndex(descriptor: SerialDescriptor): Int {
@@ -161,8 +161,10 @@ public sealed class Hocon(
161161
private fun composeName(parentName: String, childName: String) =
162162
if (parentName.isEmpty()) childName else "$parentName.$childName"
163163

164-
override fun SerialDescriptor.getTag(index: Int): String =
165-
composeName(currentTagOrNull.orEmpty(), getConventionElementName(index, useConfigNamingConvention))
164+
override fun SerialDescriptor.getTag(index: Int): String {
165+
val conventionName = getConventionElementName(index, useConfigNamingConvention)
166+
return if (!isPolymorphic) composeName(currentTagOrNull.orEmpty(), conventionName) else conventionName
167+
}
166168

167169
override fun decodeNotNullMark(): Boolean {
168170
// Tag might be null for top-level deserialization
@@ -206,6 +208,27 @@ public sealed class Hocon(
206208
}
207209
}
208210

211+
private inner class PolymorphConfigReader(private val conf: Config) : ConfigConverter<String>() {
212+
private var ind = -1
213+
214+
override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder =
215+
when {
216+
descriptor.kind.objLike -> ConfigReader(conf, isPolymorphic = true)
217+
else -> this
218+
}
219+
220+
override fun SerialDescriptor.getTag(index: Int): String = getElementName(index)
221+
222+
override fun decodeElementIndex(descriptor: SerialDescriptor): Int {
223+
ind++
224+
return if (ind >= descriptor.elementsCount) DECODE_DONE else ind
225+
}
226+
227+
override fun <E> getValueFromTaggedConfig(tag: String, valueResolver: (Config, String) -> E): E {
228+
return valueResolver(conf, tag)
229+
}
230+
}
231+
209232
private inner class ListConfigReader(private val list: ConfigList) : ConfigConverter<Int>() {
210233
private var ind = -1
211234

@@ -216,6 +239,7 @@ public sealed class Hocon(
216239

217240
override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder =
218241
when {
242+
descriptor.kind is PolymorphicKind -> PolymorphConfigReader((list[currentTag] as ConfigObject).toConfig())
219243
descriptor.kind.listLike -> ListConfigReader(list[currentTag] as ConfigList)
220244
descriptor.kind.objLike -> ConfigReader((list[currentTag] as ConfigObject).toConfig())
221245
descriptor.kind == StructureKind.MAP -> MapConfigReader(list[currentTag] as ConfigObject)
@@ -256,6 +280,7 @@ public sealed class Hocon(
256280

257281
override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder =
258282
when {
283+
descriptor.kind is PolymorphicKind -> PolymorphConfigReader((values[currentTag / 2] as ConfigObject).toConfig())
259284
descriptor.kind.listLike -> ListConfigReader(values[currentTag / 2] as ConfigList)
260285
descriptor.kind.objLike -> ConfigReader((values[currentTag / 2] as ConfigObject).toConfig())
261286
descriptor.kind == StructureKind.MAP -> MapConfigReader(values[currentTag / 2] as ConfigObject)

formats/hocon/src/test/kotlin/kotlinx/serialization/hocon/HoconPolymorphismTest.kt

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ class HoconPolymorphismTest {
2323
data class AnnotatedTypeChild(@SerialName("my_type") val type: String) : Sealed(3)
2424
}
2525

26+
@Serializable
27+
data class SealedCollectionContainer(val sealed: Collection<Sealed>)
28+
29+
@Serializable
30+
data class SealedMapContainer(val sealed: Map<String, Sealed>)
31+
2632
@Serializable
2733
data class CompositeClass(var sealed: Sealed)
2834

@@ -102,4 +108,46 @@ class HoconPolymorphismTest {
102108
serializer = Sealed.serializer(),
103109
)
104110
}
111+
112+
@Test
113+
fun testCollectionContainer() {
114+
objectHocon.assertStringFormAndRestored(
115+
expected = """
116+
sealed = [
117+
{ type = annotated_type_child, my_type = override, intField = 3 }
118+
{ type = object }
119+
{ type = data_class, name = testDataClass, intField = 1 }
120+
]
121+
""".trimIndent(),
122+
original = SealedCollectionContainer(
123+
listOf(
124+
Sealed.AnnotatedTypeChild(type = "override"),
125+
Sealed.ObjectChild,
126+
Sealed.DataClassChild(name = "testDataClass"),
127+
)
128+
),
129+
serializer = SealedCollectionContainer.serializer(),
130+
)
131+
}
132+
133+
@Test
134+
fun testMapContainer() {
135+
objectHocon.assertStringFormAndRestored(
136+
expected = """
137+
sealed = {
138+
"annotated_type_child" = { type = annotated_type_child, my_type = override, intField = 3 }
139+
"object" = { type = object }
140+
"data_class" = { type = data_class, name = testDataClass, intField = 1 }
141+
}
142+
""".trimIndent(),
143+
original = SealedMapContainer(
144+
mapOf(
145+
"annotated_type_child" to Sealed.AnnotatedTypeChild(type = "override"),
146+
"object" to Sealed.ObjectChild,
147+
"data_class" to Sealed.DataClassChild(name = "testDataClass"),
148+
)
149+
),
150+
serializer = SealedMapContainer.serializer(),
151+
)
152+
}
105153
}

0 commit comments

Comments
 (0)