Skip to content

Commit a33ef02

Browse files
authored
Properly handle top-level value classes in encodeToJsonElement (#1777)
Fixes #1774
1 parent 1b2344f commit a33ef02

File tree

3 files changed

+63
-10
lines changed

3 files changed

+63
-10
lines changed

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
2+
* Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
33
*/
44
@file:OptIn(ExperimentalSerializationApi::class)
55

@@ -69,7 +69,7 @@ private sealed class AbstractJsonTreeEncoder(
6969

7070
override fun <T> encodeSerializableValue(serializer: SerializationStrategy<T>, value: T) {
7171
// Writing non-structured data (i.e. primitives) on top-level (e.g. without any tag) requires special output
72-
if (currentTagOrNull != null || serializer.descriptor.kind !is PrimitiveKind && serializer.descriptor.kind !== SerialKind.ENUM) {
72+
if (currentTagOrNull != null || !serializer.descriptor.carrierDescriptor(serializersModule).requiresTopLevelTag) {
7373
encodePolymorphically(serializer, value) { polymorphicDiscriminator = it }
7474
} else JsonPrimitiveEncoder(json, nodeConsumer).apply {
7575
encodeSerializableValue(serializer, value)
@@ -139,6 +139,9 @@ private sealed class AbstractJsonTreeEncoder(
139139
}
140140
}
141141

142+
private val SerialDescriptor.requiresTopLevelTag: Boolean
143+
get() = kind is PrimitiveKind || kind === SerialKind.ENUM
144+
142145
internal const val PRIMITIVE_TAG = "primitive" // also used in JsonPrimitiveInput
143146

144147
private class JsonPrimitiveEncoder(

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
/*
2-
* Copyright 2017-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
2+
* Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
33
*/
44
@file:OptIn(ExperimentalSerializationApi::class)
55

66
package kotlinx.serialization.json.internal
77

88
import kotlinx.serialization.*
99
import kotlinx.serialization.descriptors.*
10-
import kotlinx.serialization.json.Json
11-
import kotlinx.serialization.modules.SerializersModule
12-
import kotlin.jvm.JvmField
10+
import kotlinx.serialization.json.*
11+
import kotlinx.serialization.modules.*
12+
import kotlin.jvm.*
1313

1414
internal enum class WriteMode(@JvmField val begin: Char, @JvmField val end: Char) {
1515
OBJ(BEGIN_OBJ, END_OBJ),
@@ -47,6 +47,6 @@ internal inline fun <T, R1 : T, R2 : T> Json.selectMapMode(
4747

4848
internal fun SerialDescriptor.carrierDescriptor(module: SerializersModule): SerialDescriptor = when {
4949
kind == SerialKind.CONTEXTUAL -> module.getContextualDescriptor(this)?.carrierDescriptor(module) ?: this
50-
isInline -> getElementDescriptor(0)
50+
isInline -> getElementDescriptor(0).carrierDescriptor(module)
5151
else -> this
5252
}

formats/json/commonTest/src/kotlinx/serialization/features/inline/InlineClassesTest.kt

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import kotlinx.serialization.*
1515
import kotlinx.serialization.builtins.*
1616
import kotlinx.serialization.encoding.*
1717
import kotlinx.serialization.json.*
18+
import kotlinx.serialization.modules.*
1819
import kotlinx.serialization.test.*
1920
import kotlin.jvm.*
2021
import kotlin.test.*
@@ -72,11 +73,60 @@ value class ResourceId(val id: String)
7273
value class ResourceType(val type: String)
7374

7475
@Serializable
75-
data class ResourceIdentifier(val id: ResourceId, val type: ResourceType)
76+
@JvmInline
77+
value class ResourceKind(val kind: SampleEnum)
78+
79+
@Serializable
80+
data class ResourceIdentifier(val id: ResourceId, val type: ResourceType, val type2: ValueWrapper)
81+
82+
@Serializable @JvmInline
83+
value class ValueWrapper(val wrapped: ResourceType)
7684

7785
class InlineClassesTest : JsonTestBase() {
7886
private val precedent: UInt = Int.MAX_VALUE.toUInt() + 10.toUInt()
7987

88+
@Test
89+
fun testTopLevel() = noLegacyJs {
90+
assertJsonFormAndRestored(
91+
ResourceType.serializer(),
92+
ResourceType("foo"),
93+
""""foo"""",
94+
)
95+
}
96+
97+
@Test
98+
fun testTopLevelOverEnum() = noLegacyJs {
99+
assertJsonFormAndRestored(
100+
ResourceKind.serializer(),
101+
ResourceKind(SampleEnum.OptionC),
102+
""""OptionC"""",
103+
)
104+
}
105+
106+
@Test
107+
fun testTopLevelWrapper() = noLegacyJs {
108+
assertJsonFormAndRestored(
109+
ValueWrapper.serializer(),
110+
ValueWrapper(ResourceType("foo")),
111+
""""foo"""",
112+
)
113+
}
114+
115+
@Test
116+
fun testTopLevelContextual() = noLegacyJs {
117+
val module = SerializersModule {
118+
contextual<ResourceType>(ResourceType.serializer())
119+
}
120+
val json = Json(default) { serializersModule = module }
121+
assertJsonFormAndRestored(
122+
ContextualSerializer(ResourceType::class),
123+
ResourceType("foo"),
124+
""""foo"""",
125+
json
126+
)
127+
}
128+
129+
80130
@Test
81131
fun testSimpleContainer() = noLegacyJs {
82132
assertJsonFormAndRestored(
@@ -106,8 +156,8 @@ class InlineClassesTest : JsonTestBase() {
106156
fun testInlineClassesWithStrings() = noLegacyJs {
107157
assertJsonFormAndRestored(
108158
ResourceIdentifier.serializer(),
109-
ResourceIdentifier(ResourceId("resId"), ResourceType("resType")),
110-
"""{"id":"resId","type":"resType"}"""
159+
ResourceIdentifier(ResourceId("resId"), ResourceType("resType"), ValueWrapper(ResourceType("wrappedType"))),
160+
"""{"id":"resId","type":"resType","type2":"wrappedType"}"""
111161
)
112162
}
113163

0 commit comments

Comments
 (0)