Skip to content

Commit cf71e08

Browse files
authored
Fix TaggedDecoder nullable decoding (#2456)
Make the TaggedDecoder.decodeNullableSerializableElement implementation consistent with AbstractDecoder, so it is possible to differentiate nullable and non-nullable serializers. Fixes #2455
1 parent b44f03f commit cf71e08

File tree

4 files changed

+71
-14
lines changed

4 files changed

+71
-14
lines changed

core/commonMain/src/kotlinx/serialization/encoding/AbstractDecoder.kt

+2-3
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,7 @@ public abstract class AbstractDecoder : Decoder, CompositeDecoder {
7474
index: Int,
7575
deserializer: DeserializationStrategy<T?>,
7676
previousValue: T?
77-
): T? {
78-
val isNullabilitySupported = deserializer.descriptor.isNullable
79-
return if (isNullabilitySupported || decodeNotNullMark()) decodeSerializableValue(deserializer, previousValue) else decodeNull()
77+
): T? = decodeIfNullable(deserializer) {
78+
decodeSerializableValue(deserializer, previousValue)
8079
}
8180
}

core/commonMain/src/kotlinx/serialization/encoding/Decoding.kt

+8-3
Original file line numberDiff line numberDiff line change
@@ -260,12 +260,17 @@ public interface Decoder {
260260
* Decodes the nullable value of type [T] by delegating the decoding process to the given [deserializer].
261261
*/
262262
@ExperimentalSerializationApi
263-
public fun <T : Any> decodeNullableSerializableValue(deserializer: DeserializationStrategy<T?>): T? {
264-
val isNullabilitySupported = deserializer.descriptor.isNullable
265-
return if (isNullabilitySupported || decodeNotNullMark()) decodeSerializableValue(deserializer) else decodeNull()
263+
public fun <T : Any> decodeNullableSerializableValue(deserializer: DeserializationStrategy<T?>): T? = decodeIfNullable(deserializer) {
264+
decodeSerializableValue(deserializer)
266265
}
267266
}
268267

268+
@OptIn(ExperimentalSerializationApi::class)
269+
internal inline fun <T : Any> Decoder.decodeIfNullable(deserializer: DeserializationStrategy<T?>, block: () -> T?): T? {
270+
val isNullabilitySupported = deserializer.descriptor.isNullable
271+
return if (isNullabilitySupported || decodeNotNullMark()) block() else decodeNull()
272+
}
273+
269274
/**
270275
* [CompositeDecoder] is a part of decoding process that is bound to a particular structured part of
271276
* the serialized form, described by the serial descriptor passed to [Decoder.beginStructure].

core/commonMain/src/kotlinx/serialization/internal/Tagged.kt

+4-7
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,6 @@ public abstract class TaggedDecoder<Tag : Any?> : Decoder, CompositeDecoder {
206206
protected open fun <T : Any?> decodeSerializableValue(deserializer: DeserializationStrategy<T>, previousValue: T?): T =
207207
decodeSerializableValue(deserializer)
208208

209-
210209
// ---- Implementation of low-level API ----
211210

212211
override fun decodeInline(descriptor: SerialDescriptor): Decoder =
@@ -284,13 +283,11 @@ public abstract class TaggedDecoder<Tag : Any?> : Decoder, CompositeDecoder {
284283
index: Int,
285284
deserializer: DeserializationStrategy<T?>,
286285
previousValue: T?
287-
): T? =
288-
tagBlock(descriptor.getTag(index)) {
289-
if (decodeNotNullMark()) decodeSerializableValue(
290-
deserializer,
291-
previousValue
292-
) else decodeNull()
286+
): T? = tagBlock(descriptor.getTag(index)) {
287+
decodeIfNullable(deserializer) {
288+
decodeSerializableValue(deserializer, previousValue)
293289
}
290+
}
294291

295292
private fun <E> tagBlock(tag: Tag, block: () -> E): E {
296293
pushTag(tag)

formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonElementDecodingTest.kt

+57-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package kotlinx.serialization.json
22

3-
import kotlinx.serialization.Serializable
3+
import kotlinx.serialization.*
4+
import kotlinx.serialization.descriptors.*
5+
import kotlinx.serialization.encoding.*
46
import kotlin.test.*
57

68
class JsonElementDecodingTest : JsonTestBase() {
@@ -51,4 +53,58 @@ class JsonElementDecodingTest : JsonTestBase() {
5153
json = json.replace("%", "0")
5254
Json.parseToJsonElement(json)
5355
}
56+
57+
private open class NullAsElementSerializer<T : Any>(private val serializer: KSerializer<T>, val nullElement: T) : KSerializer<T?> {
58+
final override val descriptor: SerialDescriptor = serializer.descriptor.nullable
59+
60+
final override fun serialize(encoder: Encoder, value: T?) {
61+
serializer.serialize(encoder, value ?: nullElement)
62+
}
63+
64+
final override fun deserialize(decoder: Decoder): T = serializer.deserialize(decoder)
65+
}
66+
67+
private object NullAsJsonNullJsonElementSerializer : NullAsElementSerializer<JsonElement>(JsonElement.serializer(), JsonNull)
68+
private object NullAsJsonNullJsonPrimitiveSerializer : NullAsElementSerializer<JsonPrimitive>(JsonPrimitive.serializer(), JsonNull)
69+
private object NullAsJsonNullJsonNullSerializer : NullAsElementSerializer<JsonNull>(JsonNull.serializer(), JsonNull)
70+
private val noExplicitNullsOrDefaultsJson = Json {
71+
explicitNulls = false
72+
encodeDefaults = false
73+
}
74+
75+
@Test
76+
fun testNullableJsonElementDecoding() {
77+
@Serializable
78+
data class Wrapper(
79+
@Serializable(NullAsJsonNullJsonElementSerializer::class)
80+
val value: JsonElement? = null,
81+
)
82+
83+
assertJsonFormAndRestored(Wrapper.serializer(), Wrapper(value = JsonNull), """{"value":null}""", noExplicitNullsOrDefaultsJson)
84+
assertJsonFormAndRestored(Wrapper.serializer(), Wrapper(value = null), """{}""", noExplicitNullsOrDefaultsJson)
85+
}
86+
87+
@Test
88+
fun testNullableJsonPrimitiveDecoding() {
89+
@Serializable
90+
data class Wrapper(
91+
@Serializable(NullAsJsonNullJsonPrimitiveSerializer::class)
92+
val value: JsonPrimitive? = null,
93+
)
94+
95+
assertJsonFormAndRestored(Wrapper.serializer(), Wrapper(value = JsonNull), """{"value":null}""", noExplicitNullsOrDefaultsJson)
96+
assertJsonFormAndRestored(Wrapper.serializer(), Wrapper(value = null), """{}""", noExplicitNullsOrDefaultsJson)
97+
}
98+
99+
@Test
100+
fun testNullableJsonNullDecoding() {
101+
@Serializable
102+
data class Wrapper(
103+
@Serializable(NullAsJsonNullJsonNullSerializer::class)
104+
val value: JsonNull? = null,
105+
)
106+
107+
assertJsonFormAndRestored(Wrapper.serializer(), Wrapper(value = JsonNull), """{"value":null}""", noExplicitNullsOrDefaultsJson)
108+
assertJsonFormAndRestored(Wrapper.serializer(), Wrapper(value = null), """{}""", noExplicitNullsOrDefaultsJson)
109+
}
54110
}

0 commit comments

Comments
 (0)