Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit bb9f94e

Browse files
committedJul 3, 2024
Add a complete example with a lot of element types (value classes, primitives, sub-structures and sub-lists)
1 parent 9407faf commit bb9f94e

File tree

3 files changed

+335
-56
lines changed

3 files changed

+335
-56
lines changed
 

‎core/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ kotlin {
3737
Require-Kotlin-Version is used to determine whether runtime library with new features can work with old compilers.
3838
In ideal case, its value should always be 1.4, but some refactorings (e.g. adding a method to the Encoder interface)
3939
may unexpectedly break old compilers, so it is left out as a safety net. Compiler plugins, starting from 1.4 are instructed
40-
to reject runtime if runtime's Require-Kotlin-Version is greater then the current compiler.
40+
to reject runtime if runtime's Require-Kotlin-Version is greater than the current compiler.
4141
*/
4242
tasks.withType<Jar>().named(kotlin.jvm().artifactsTaskName) {
4343

‎core/commonMain/src/kotlinx/serialization/encoding/ReorderingCompositeEncoder.kt

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,14 @@ import kotlinx.serialization.modules.*
2222
* @param mapElementIndex maps the element index to a new positional zero-based index. If this mapper provides the same index for multiple elements, only the last one will be encoded as the previous ones will be overridden. The mapped index just helps to reorder the elements, but the reordered `encode*Element` method calls will still pass the original element index.
2323
*/
2424
@ExperimentalSerializationApi
25-
internal class ReorderingCompositeEncoder(
25+
public class ReorderingCompositeEncoder(
2626
structureDescriptor: SerialDescriptor,
2727
private val compositeEncoderDelegate: CompositeEncoder,
2828
private val mapElementIndex: (SerialDescriptor, Int) -> Int,
2929
) : CompositeEncoder {
30-
private var bufferedCalls = Array<BufferedCall?>(structureDescriptor.elementsCount) { null }
30+
// TODO we should use a map to have buffered calls per descriptor if the same encoder is reused for different structures
31+
// or we need to ensure that this encoder is not reused
32+
private val bufferedCalls = Array<BufferedCall?>(structureDescriptor.elementsCount) { null }
3133

3234
override val serializersModule: SerializersModule
3335
// No need to return a serializers module as it's not used during buffering
@@ -48,6 +50,7 @@ internal class ReorderingCompositeEncoder(
4850

4951
override fun endStructure(descriptor: SerialDescriptor) {
5052
encodeBufferedFields(descriptor)
53+
// TODO should we clear the buffer after encoding? Is it possible to reuse this encoder?
5154
}
5255

5356
private fun encodeBufferedFields(descriptor: SerialDescriptor) {
@@ -228,19 +231,7 @@ internal class ReorderingCompositeEncoder(
228231

229232
private fun invalidCall(methodName: String): Nothing {
230233
// This method is normally called by encodeSerializableValue or encodeNullableSerializableValue that is buffered, so we should never go here during buffering as it will be delegated to the concrete CompositeEncoder
231-
throw UnsupportedOperationException("$methodName should not be called when reordering fields, as it should be done by encodeSerializableValue or encodeNullableSerializableValue")
234+
throw UnsupportedOperationException("encodeSerializableValue or encodeNullableSerializableValue should be called before $methodName")
232235
}
233236
}
234237
}
235-
236-
/**
237-
* Encodes the [structureDescriptor] elements in a specific order provided by [elementIndexMapper].
238-
*
239-
* @param structureDescriptor descriptor of the structure being encoded and reordered
240-
* @param elementIndexMapper maps the element index to a new positional zero-based index. If this mapper provides the same index for multiple elements, only the last one will be encoded as the previous ones will be overridden. The mapped index just helps to reorder the elements, but the reordered `encode*Element` method calls will still pass the original element index.
241-
*/
242-
@ExperimentalSerializationApi
243-
public fun CompositeEncoder.encodeReorderingElements(
244-
structureDescriptor: SerialDescriptor,
245-
elementIndexMapper: (SerialDescriptor, Int) -> Int,
246-
): CompositeEncoder = ReorderingCompositeEncoder(structureDescriptor, this, elementIndexMapper)

‎core/commonTest/src/kotlinx/serialization/encoding/ReorderingCompositeEncoderTest.kt

Lines changed: 328 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -13,60 +13,348 @@ import kotlin.test.*
1313
class ReorderingCompositeEncoderTest {
1414
@Test
1515
fun shouldReorderWell() {
16-
val first = 42
17-
val second = "string"
18-
val third = true
19-
val value = OuterToBeReordered(Middle(Inner(second, third)), first)
16+
val value = AllTypesExample(
17+
int = 1,
18+
nullableInt = null,
19+
intWrapped = IntValue(2),
20+
nullableIntWrapped = IntNullableValue(null),
21+
intWrappedNullable = IntValue(3),
2022

21-
val encoder = ReorderingEncoder().apply {
22-
encodeSerializableValue(OuterToBeReordered.serializer(), value)
23-
}
23+
string = "Hello",
24+
nullableString = null,
25+
stringWrapped = StringValue("World"),
26+
nullableStringWrapped = StringNullableValue(null),
27+
stringWrappedNullable = StringValue("!!!"),
28+
29+
boolean = true,
30+
nullableBoolean = null,
31+
booleanWrapped = BooleanValue(true),
32+
nullableBooleanWrapped = BooleanNullableValue(null),
33+
booleanWrappedNullable = BooleanValue(false),
34+
35+
double = 1.0,
36+
nullableDouble = null,
37+
doubleWrapped = DoubleValue(2.0),
38+
nullableDoubleWrapped = DoubleNullableValue(null),
39+
doubleWrappedNullable = DoubleValue(3.0),
40+
41+
nonReorderedList = listOf(1, 2, 3),
42+
nonReorderedNullableList = null,
43+
nonReorderedListWrapped = ListValue(listOf(4, 5, 6)),
44+
nonReorderedNullableListWrapped = ListNullableValue(null),
45+
nonReorderedListWrappedNullable = ListNullableValue(listOf(7, 8, 9)),
46+
nonReorderedListWrappedNullableValues = ListNullableValues(listOf(null, 8, 9)),
47+
48+
float = 1.0f,
49+
nullableFloat = null,
50+
floatWrapped = FloatValue(2.0f),
51+
nullableFloatWrapped = FloatNullableValue(null),
52+
floatWrappedNullable = FloatValue(3.0f),
53+
54+
long = 1L,
55+
nullableLong = null,
56+
longWrapped = LongValue(2L),
57+
nullableLongWrapped = LongNullableValue(null),
58+
longWrappedNullable = LongValue(3L),
59+
60+
short = 1,
61+
nullableShort = null,
62+
shortWrapped = ShortValue(2),
63+
nullableShortWrapped = ShortNullableValue(null),
64+
shortWrappedNullable = ShortValue(3),
65+
66+
nonReorderedSubStructure = SubStructure(
67+
a = IntValue(10),
68+
b = "Sub",
69+
c = null,
70+
d = null
71+
),
72+
nonReorderedNullableSubStructure = null,
73+
nonReorderedSubStructureWrapped = SubStructureValue(
74+
SubStructure(
75+
a = IntValue(11),
76+
b = "Wrapped",
77+
c = 12L,
78+
d = 13.toByte()
79+
)
80+
),
81+
nonReorderedNullableSubStructureWrapped = SubStructureNullableValue(null),
82+
nonReorderedSubStructureWrappedNullable = SubStructureValue(
83+
SubStructure(
84+
a = IntValue(14),
85+
b = "Nullable Wrapped",
86+
c = 15L,
87+
d = 16.toByte()
88+
)
89+
),
90+
91+
byte = 1,
92+
nullableByte = null,
93+
byteWrapped = ByteValue(2),
94+
nullableByteWrapped = ByteNullableValue(null),
95+
byteWrappedNullable = ByteValue(3),
2496

25-
assertContentEquals(actual = encoder.encodedValues, expected = listOf(first, second, third))
97+
char = 'A',
98+
nullableChar = null,
99+
charWrapped = CharValue('B'),
100+
nullableCharWrapped = CharNullableValue(null),
101+
charWrappedNullable = CharValue('C')
102+
)
103+
104+
val output = StringBuilder()
105+
val encoder = LightJsonEncoder(
106+
output,
107+
descriptorToReorder = AllTypesExample.serializer().descriptor
108+
) { descriptor, index -> descriptor.elementsCount - 1 - index }
109+
110+
encoder.encodeSerializableValue(AllTypesExample.serializer(), value)
111+
112+
// the final output should encode the fields in the reverse order, but should not reorder sub-structures
113+
assertEquals(
114+
actual = output.toString(),
115+
expected = """
116+
{charWrappedNullable:"C", nullableCharWrapped:null, charWrapped:"B", nullableChar:null, char:"A", byteWrappedNullable:3, nullableByteWrapped:null, byteWrapped:2, nullableByte:null, byte:1, nonReorderedSubStructureWrappedNullable:{a:14, b:"Nullable Wrapped", c:15, d:16}, nonReorderedNullableSubStructureWrapped:null, nonReorderedSubStructureWrapped:{a:11, b:"Wrapped", c:12, d:13}, nonReorderedNullableSubStructure:null, nonReorderedSubStructure:{a:10, b:"Sub", c:null, d:null}, shortWrappedNullable:3, nullableShortWrapped:null, shortWrapped:2, nullableShort:null, short:1, longWrappedNullable:3, nullableLongWrapped:null, longWrapped:2, nullableLong:null, long:1, floatWrappedNullable:3.0, nullableFloatWrapped:null, floatWrapped:2.0, nullableFloat:null, float:1.0, nonReorderedListWrappedNullableValues:[0:null, 1:8, 2:9], nonReorderedListWrappedNullable:[0:7, 1:8, 2:9], nonReorderedNullableListWrapped:null, nonReorderedListWrapped:[0:4, 1:5, 2:6], nonReorderedNullableList:null, nonReorderedList:[0:1, 1:2, 2:3], doubleWrappedNullable:3.0, nullableDoubleWrapped:null, doubleWrapped:2.0, nullableDouble:null, double:1.0, booleanWrappedNullable:false, nullableBooleanWrapped:null, booleanWrapped:true, nullableBoolean:null, boolean:true, stringWrappedNullable:"!!!", nullableStringWrapped:null, stringWrapped:"World", nullableString:null, string:"Hello", intWrappedNullable:3, nullableIntWrapped:null, intWrapped:2, nullableInt:null, int:1}
117+
""".trimIndent(),
118+
)
26119
}
120+
}
27121

28-
private class ReorderingEncoder(
29-
val encodedValues: MutableList<Any?> = mutableListOf()
30-
) : AbstractEncoder() {
31-
override val serializersModule: SerializersModule
32-
get() = EmptySerializersModule()
122+
@Serializable
123+
data class AllTypesExample(
124+
val int: Int,
125+
val nullableInt: Int?,
126+
val intWrapped: IntValue,
127+
val nullableIntWrapped: IntNullableValue,
128+
val intWrappedNullable: IntValue?,
33129

34-
override fun encodeValue(value: Any) {
35-
encodedValues.add(value)
36-
}
130+
val string: String,
131+
val nullableString: String?,
132+
val stringWrapped: StringValue,
133+
val nullableStringWrapped: StringNullableValue,
134+
val stringWrappedNullable: StringValue?,
135+
136+
val boolean: Boolean,
137+
val nullableBoolean: Boolean?,
138+
val booleanWrapped: BooleanValue,
139+
val nullableBooleanWrapped: BooleanNullableValue,
140+
val booleanWrappedNullable: BooleanValue?,
141+
142+
val double: Double,
143+
val nullableDouble: Double?,
144+
val doubleWrapped: DoubleValue,
145+
val nullableDoubleWrapped: DoubleNullableValue,
146+
val doubleWrappedNullable: DoubleValue?,
147+
148+
val nonReorderedList: List<Int>,
149+
val nonReorderedNullableList: List<Int>?,
150+
val nonReorderedListWrapped: ListValue,
151+
val nonReorderedNullableListWrapped: ListNullableValue,
152+
val nonReorderedListWrappedNullable: ListNullableValue?,
153+
val nonReorderedListWrappedNullableValues: ListNullableValues?,
154+
155+
val float: Float,
156+
val nullableFloat: Float?,
157+
val floatWrapped: FloatValue,
158+
val nullableFloatWrapped: FloatNullableValue,
159+
val floatWrappedNullable: FloatValue?,
160+
161+
val long: Long,
162+
val nullableLong: Long?,
163+
val longWrapped: LongValue,
164+
val nullableLongWrapped: LongNullableValue,
165+
val longWrappedNullable: LongValue?,
166+
167+
val short: Short,
168+
val nullableShort: Short?,
169+
val shortWrapped: ShortValue,
170+
val nullableShortWrapped: ShortNullableValue,
171+
val shortWrappedNullable: ShortValue?,
172+
173+
val nonReorderedSubStructure: SubStructure,
174+
val nonReorderedNullableSubStructure: SubStructure?,
175+
val nonReorderedSubStructureWrapped: SubStructureValue,
176+
val nonReorderedNullableSubStructureWrapped: SubStructureNullableValue,
177+
val nonReorderedSubStructureWrappedNullable: SubStructureValue?,
178+
179+
val byte: Byte,
180+
val nullableByte: Byte?,
181+
val byteWrapped: ByteValue,
182+
val nullableByteWrapped: ByteNullableValue,
183+
val byteWrappedNullable: ByteValue?,
184+
185+
val char: Char,
186+
val nullableChar: Char?,
187+
val charWrapped: CharValue,
188+
val nullableCharWrapped: CharNullableValue,
189+
val charWrappedNullable: CharValue?,
190+
)
191+
192+
@Serializable
193+
data class SubStructure(
194+
val a: IntValue,
195+
val b: String,
196+
val c: Long?,
197+
val d: Byte?,
198+
)
199+
200+
@Serializable
201+
@JvmInline
202+
value class IntValue(val value: Int)
203+
204+
@Serializable
205+
@JvmInline
206+
value class StringValue(val value: String)
207+
208+
@Serializable
209+
@JvmInline
210+
value class BooleanValue(val value: Boolean)
211+
212+
@Serializable
213+
@JvmInline
214+
value class DoubleValue(val value: Double)
215+
216+
@Serializable
217+
@JvmInline
218+
value class FloatValue(val value: Float)
219+
220+
@Serializable
221+
@JvmInline
222+
value class LongValue(val value: Long)
223+
224+
@Serializable
225+
@JvmInline
226+
value class ShortValue(val value: Short)
227+
228+
@Serializable
229+
@JvmInline
230+
value class ByteValue(val value: Byte)
231+
232+
@Serializable
233+
@JvmInline
234+
value class CharValue(val value: Char)
37235

38-
override fun encodeNull() {
39-
encodedValues.add(null)
236+
@Serializable
237+
@JvmInline
238+
value class SubStructureValue(val value: SubStructure)
239+
240+
@Serializable
241+
@JvmInline
242+
value class ListValue(val value: List<Int>)
243+
244+
245+
246+
247+
248+
@Serializable
249+
@JvmInline
250+
value class IntNullableValue(val value: Int?)
251+
252+
@Serializable
253+
@JvmInline
254+
value class StringNullableValue(val value: String?)
255+
256+
@Serializable
257+
@JvmInline
258+
value class BooleanNullableValue(val value: Boolean?)
259+
260+
@Serializable
261+
@JvmInline
262+
value class DoubleNullableValue(val value: Double?)
263+
264+
@Serializable
265+
@JvmInline
266+
value class FloatNullableValue(val value: Float?)
267+
268+
@Serializable
269+
@JvmInline
270+
value class LongNullableValue(val value: Long?)
271+
272+
@Serializable
273+
@JvmInline
274+
value class ShortNullableValue(val value: Short?)
275+
276+
@Serializable
277+
@JvmInline
278+
value class ByteNullableValue(val value: Byte?)
279+
280+
@Serializable
281+
@JvmInline
282+
value class CharNullableValue(val value: Char?)
283+
284+
@Serializable
285+
@JvmInline
286+
value class SubStructureNullableValue(val value: SubStructure?)
287+
288+
@Serializable
289+
@JvmInline
290+
value class ListNullableValue(val value: List<Int>?)
291+
292+
@Serializable
293+
@JvmInline
294+
value class ListNullableValues(val value: List<Int?>)
295+
296+
297+
298+
299+
300+
private class LightJsonEncoder(
301+
val sb: StringBuilder,
302+
val descriptorToReorder: SerialDescriptor,
303+
val mapElementIndex: (SerialDescriptor, Int) -> Int,
304+
) : AbstractEncoder() {
305+
override val serializersModule: SerializersModule = EmptySerializersModule()
306+
var previousDescriptor: SerialDescriptor? = null
307+
308+
override fun beginStructure(descriptor: SerialDescriptor): CompositeEncoder {
309+
if (descriptor.kind == StructureKind.LIST) {
310+
sb.append('[')
311+
} else {
312+
sb.append('{')
313+
}
314+
previousDescriptor = null
315+
if (descriptor == descriptorToReorder) {
316+
return ReorderingCompositeEncoder(descriptor, this, mapElementIndex)
40317
}
318+
return this
319+
}
41320

42-
override fun beginStructure(descriptor: SerialDescriptor): CompositeEncoder {
43-
return BasicCompositeEncoder().encodeReorderingElements(descriptor) { _, index ->
44-
// invert fields
45-
if (index == 0) 1 else 0
46-
}
321+
override fun endStructure(descriptor: SerialDescriptor) {
322+
if (descriptor.kind == StructureKind.LIST) {
323+
sb.append(']')
324+
} else {
325+
sb.append('}')
47326
}
327+
}
48328

49-
private inner class BasicCompositeEncoder : AbstractEncoder() {
50-
override val serializersModule: SerializersModule
51-
get() = EmptySerializersModule()
329+
override fun encodeElement(descriptor: SerialDescriptor, index: Int): Boolean {
330+
if (previousDescriptor == null) {
331+
previousDescriptor = descriptor
332+
} else {
333+
previousDescriptor = descriptor
334+
sb.append(", ")
335+
}
336+
sb.append(descriptor.getElementName(index))
337+
sb.append(':')
338+
return true
339+
}
52340

53-
override fun encodeValue(value: Any) {
54-
encodedValues.add(value)
55-
}
341+
override fun encodeNull() {
342+
sb.append("null")
343+
}
56344

57-
override fun encodeNull() {
58-
encodedValues.add(null)
59-
}
60-
}
345+
override fun encodeValue(value: Any) {
346+
sb.append(value)
61347
}
62348

63-
@Serializable
64-
private data class OuterToBeReordered(val a: Middle, val b: Int)
349+
override fun encodeString(value: String) {
350+
sb.append('"')
351+
sb.append(value)
352+
sb.append('"')
353+
}
65354

66-
@JvmInline
67-
@Serializable
68-
private value class Middle(val i: Inner)
355+
override fun encodeInline(descriptor: SerialDescriptor): Encoder {
356+
return super.encodeInline(descriptor)
357+
}
69358

70-
@Serializable
71-
private data class Inner(val c: String, val d: Boolean)
359+
override fun encodeChar(value: Char) = encodeString(value.toString())
72360
}

0 commit comments

Comments
 (0)
Please sign in to comment.