Skip to content

Commit 3904bd1

Browse files
authored
Support nullable types at top-level for JsonElement decoding (#1117)
Fixes #1102
1 parent ef53d75 commit 3904bd1

File tree

4 files changed

+60
-9
lines changed

4 files changed

+60
-9
lines changed

core/api/kotlinx-serialization-core.api

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1031,7 +1031,7 @@ public abstract class kotlinx/serialization/internal/TaggedEncoder : kotlinx/ser
10311031
public final fun encodeLong (J)V
10321032
public final fun encodeLongElement (Lkotlinx/serialization/descriptors/SerialDescriptor;IJ)V
10331033
public final fun encodeNotNullMark ()V
1034-
public final fun encodeNull ()V
1034+
public fun encodeNull ()V
10351035
public final fun encodeNullableSerializableElement (Lkotlinx/serialization/descriptors/SerialDescriptor;ILkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;)V
10361036
public fun encodeNullableSerializableValue (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;)V
10371037
public final fun encodeSerializableElement (Lkotlinx/serialization/descriptors/SerialDescriptor;ILkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;)V
@@ -1048,7 +1048,6 @@ public abstract class kotlinx/serialization/internal/TaggedEncoder : kotlinx/ser
10481048
protected fun encodeTaggedFloat (Ljava/lang/Object;F)V
10491049
protected fun encodeTaggedInt (Ljava/lang/Object;I)V
10501050
protected fun encodeTaggedLong (Ljava/lang/Object;J)V
1051-
protected fun encodeTaggedNotNullMark (Ljava/lang/Object;)V
10521051
protected fun encodeTaggedNull (Ljava/lang/Object;)V
10531052
protected fun encodeTaggedShort (Ljava/lang/Object;S)V
10541053
protected fun encodeTaggedString (Ljava/lang/Object;Ljava/lang/String;)V

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ public abstract class TaggedEncoder<Tag : Any?> : Encoder, CompositeEncoder {
3030
protected open fun encodeTaggedValue(tag: Tag, value: Any): Unit =
3131
throw SerializationException("Non-serializable ${value::class} is not supported by ${this::class} encoder")
3232

33-
protected open fun encodeTaggedNotNullMark(tag: Tag) {}
3433
protected open fun encodeTaggedNull(tag: Tag): Unit = throw SerializationException("null is not supported")
3534
protected open fun encodeTaggedInt(tag: Tag, value: Int): Unit = encodeTaggedValue(tag, value)
3635
protected open fun encodeTaggedByte(tag: Tag, value: Byte): Unit = encodeTaggedValue(tag, value)
@@ -56,8 +55,8 @@ public abstract class TaggedEncoder<Tag : Any?> : Encoder, CompositeEncoder {
5655
return true
5756
}
5857

59-
final override fun encodeNotNullMark(): Unit = encodeTaggedNotNullMark(currentTag)
60-
final override fun encodeNull(): Unit = encodeTaggedNull(popTag())
58+
final override fun encodeNotNullMark() {} // Does nothing, open because is not really required
59+
open override fun encodeNull(): Unit = encodeTaggedNull(popTag())
6160
final override fun encodeBoolean(value: Boolean): Unit = encodeTaggedBoolean(popTag(), value)
6261
final override fun encodeByte(value: Byte): Unit = encodeTaggedByte(popTag(), value)
6362
final override fun encodeShort(value: Short): Unit = encodeTaggedShort(popTag(), value)

formats/json/commonMain/src/kotlinx/serialization/json/internal/TreeJsonEncoder.kt

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ internal fun <T> Json.writeJson(value: T, serializer: SerializationStrategy<T>):
2424
@ExperimentalSerializationApi
2525
private sealed class AbstractJsonTreeEncoder(
2626
final override val json: Json,
27-
val nodeConsumer: (JsonElement) -> Unit
27+
private val nodeConsumer: (JsonElement) -> Unit
2828
) : NamedValueEncoder(), JsonEncoder {
2929

3030
final override val serializersModule: SerializersModule
@@ -39,11 +39,19 @@ private sealed class AbstractJsonTreeEncoder(
3939
encodeSerializableValue(JsonElementSerializer, element)
4040
}
4141

42-
override fun shouldEncodeElementDefault(descriptor: SerialDescriptor, index: Int): Boolean = configuration.encodeDefaults
42+
override fun shouldEncodeElementDefault(descriptor: SerialDescriptor, index: Int): Boolean =
43+
configuration.encodeDefaults
44+
4345
override fun composeName(parentName: String, childName: String): String = childName
4446
abstract fun putElement(key: String, element: JsonElement)
4547
abstract fun getCurrent(): JsonElement
4648

49+
50+
override fun encodeNull() {
51+
val tag = currentTagOrNull ?: return nodeConsumer(JsonNull)
52+
encodeTaggedNull(tag)
53+
}
54+
4755
override fun encodeTaggedNull(tag: String) = putElement(tag, JsonNull)
4856

4957
override fun encodeTaggedInt(tag: String, value: Int) = putElement(tag, JsonPrimitive(value))
@@ -120,8 +128,10 @@ private sealed class AbstractJsonTreeEncoder(
120128

121129
internal const val PRIMITIVE_TAG = "primitive" // also used in JsonPrimitiveInput
122130

123-
private class JsonPrimitiveEncoder(json: Json, nodeConsumer: (JsonElement) -> Unit) :
124-
AbstractJsonTreeEncoder(json, nodeConsumer) {
131+
private class JsonPrimitiveEncoder(
132+
json: Json,
133+
nodeConsumer: (JsonElement) -> Unit
134+
) : AbstractJsonTreeEncoder(json, nodeConsumer) {
125135
private var content: JsonElement? = null
126136

127137
init {
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package kotlinx.serialization.json
2+
3+
import kotlinx.serialization.Serializable
4+
import kotlin.test.*
5+
6+
class JsonElementDecodingTest : JsonTestBase() {
7+
8+
@Serializable
9+
data class A(val a: Int = 42)
10+
11+
@Test
12+
fun testTopLevelClass() = assertSerializedForm(A(), """{}""".trimMargin())
13+
14+
@Test
15+
fun testTopLevelNullableClass() {
16+
assertSerializedForm<A?>(A(), """{}""")
17+
assertSerializedForm<A?>(null, "null")
18+
}
19+
20+
@Test
21+
fun testTopLevelPrimitive() = assertSerializedForm(42, """42""")
22+
23+
@Test
24+
fun testTopLevelNullablePrimitive() {
25+
assertSerializedForm<Int?>(42, """42""")
26+
assertSerializedForm<Int?>(null, """null""")
27+
}
28+
29+
@Test
30+
fun testTopLevelList() = assertSerializedForm(listOf(42), """[42]""")
31+
32+
@Test
33+
fun testTopLevelNullableList() {
34+
assertSerializedForm<List<Int>?>(listOf(42), """[42]""")
35+
assertSerializedForm<List<Int>?>(null, """null""")
36+
}
37+
38+
private inline fun <reified T> assertSerializedForm(value: T, expectedString: String) {
39+
val element = Json.encodeToJsonElement(value)
40+
assertEquals(expectedString, element.toString())
41+
assertEquals(value, Json.decodeFromJsonElement(element))
42+
}
43+
}

0 commit comments

Comments
 (0)