Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
Original file line number Diff line number Diff line change
Expand Up @@ -95,12 +95,19 @@ class JsonTransformingSerializerTest : JsonTestBase() {
@Test
fun testDocumentationSample() = parametrizedTest { streaming ->
val correctExample = DocExample("str1")
assertEquals(correctExample, json.decodeFromString(DocExample.serializer(), """{"data":["str1"]}""", streaming))
assertEquals(correctExample, json.decodeFromString(DocExample.serializer(), """{"data":"str1"}""", streaming))
assertEquals(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please do not add irrelevant changes to the PR, especially if they're about formatting.

correctExample,
json.decodeFromString(DocExample.serializer(), """{"data":["str1"]}""", streaming)
)
assertEquals(
correctExample,
json.decodeFromString(DocExample.serializer(), """{"data":"str1"}""", streaming)
)
}

// Wraps/unwraps {"data":null} to just `null`, because StringData.data is not nullable
object NullableStringDataSerializer : JsonTransformingSerializer<StringData?>(StringData.serializer().nullable) {
object NullableStringDataSerializer :
JsonTransformingSerializer<StringData?>(StringData.serializer().nullable) {
override fun transformDeserialize(element: JsonElement): JsonElement {
val data = element.jsonObject["data"]?.jsonPrimitive
if (data?.contentOrNull.isNullOrBlank()) return JsonNull
Expand All @@ -120,7 +127,64 @@ class JsonTransformingSerializerTest : JsonTestBase() {
fun testNullableTransformingSerializer() {
val normalInput = """{"stringData":{"data":"str1"}}"""
val nullInput = """{"stringData":{"data":null}}"""
assertJsonFormAndRestored(NullableStringDataHolder.serializer(), NullableStringDataHolder(StringData("str1")), normalInput)
assertJsonFormAndRestored(NullableStringDataHolder.serializer(), NullableStringDataHolder(null), nullInput)
assertJsonFormAndRestored(
NullableStringDataHolder.serializer(),
NullableStringDataHolder(StringData("str1")),
normalInput
)
assertJsonFormAndRestored(
NullableStringDataHolder.serializer(),
NullableStringDataHolder(null),
nullInput
)
}

@Serializable
sealed class BaseExample

@Serializable(SubExample.PolymorphicSerializer::class)
@KeepGeneratedSerializer
@SerialName("Sub")
data class SubExample(
val data: String
) : BaseExample() {
object PolymorphicSerializer : JsonTransformingSerializer<SubExample>(generatedSerializer())
}

@Test
fun testPolymorphicExampleCanBeParsed() {
val baseExample: BaseExample = SubExample("str1")
val polymorphicInput = Json.encodeToString(baseExample)
assertEquals(baseExample, Json.decodeFromString(polymorphicInput))
}

@Serializable
sealed class NestedBaseExample

@Serializable
@SerialName("leaf")
data class LeafSubExample(val value: String) : NestedBaseExample()

@Serializable
@SerialName("composite")
data class CompositeSubExample(
val name: String,
val children: List<NestedBaseExample>,
val metadata: NestedBaseExample? = null
) : NestedBaseExample()

@Test
fun testNestedPolymorphicExampleCanBeParsed() {
val root: NestedBaseExample = CompositeSubExample(
name = "root",
children = listOf(
LeafSubExample("child1"),
CompositeSubExample("sub-root", listOf(LeafSubExample("grand-child")))
),
metadata = LeafSubExample("meta-info")
)

val polymorphicInput = Json.encodeToString(root)
assertEquals(root, Json.decodeFromString(polymorphicInput))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import kotlin.jvm.*
@JsonFriendModuleApi
public fun <T> readJson(json: Json, element: JsonElement, deserializer: DeserializationStrategy<T>): T {
val input = when (element) {
is JsonObject -> JsonTreeDecoder(json, element)
is JsonObject -> JsonTreeDecoder(json, element, deserializer.descriptor.classDiscriminator(json))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sadly, this change is too broad to fix the problem. By using classDiscriminator(json), you're adding a discriminator to every JsonTreeDecoder, even one that was not originally polymorphic. Consider this class:

@Serializable(SubExample2.PolymorphicSerializer::class)
@KeepGeneratedSerializer
@SerialName("Sub")
data class SubExample2(
    val data: String
) {
    object PolymorphicSerializer : JsonTransformingSerializer<SubExample2>(generatedSerializer())
}

It is almost identical to yours, but it does not have a superclass and therefore is not polymorphic. Therefore, we expect this test to fail:

@Test
fun testNonPolymorphicExampleShouldNotBeParsed() {
    val input = """{"type":"Sub","data":"str1"}"""
    val j = Json { ignoreUnknownKeys = false }
    println(j.decodeFromString<SubExample2>(input)) // should throw "Encountered an unknown key 'type'"
}

However, with your change, this test passes.

is JsonArray -> JsonTreeListDecoder(json, element)
is JsonLiteral, JsonNull -> JsonPrimitiveDecoder(json, element as JsonPrimitive)
}
Expand Down