Skip to content

Commit 139d433

Browse files
committed
feat: Add a way to reorder elements during encoding
1 parent 0013192 commit 139d433

File tree

1 file changed

+240
-0
lines changed

1 file changed

+240
-0
lines changed
Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
/*
2+
* Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package kotlinx.serialization.encoding
6+
7+
import kotlinx.serialization.*
8+
import kotlinx.serialization.descriptors.*
9+
import kotlinx.serialization.modules.*
10+
11+
12+
/**
13+
* Encodes composite elements in a specific order managed by [mapElementIndex].
14+
*
15+
* This encoder will replicate the behavior of a standard encoding, but calling the `encode*Element` methods in
16+
* the order defined by [mapElementIndex]. It first buffers each `encode*Element` calls in an array following
17+
* the given indexes using [mapElementIndex], then when [endStructure] is called, it encodes the buffered calls
18+
* in the expected order by replaying the previous calls on a concrete [CompositeEncoder] provided
19+
* by [beginStructure].
20+
*
21+
* @param structureDescriptor descriptor of the structure being encoded
22+
* @param beginStructure provides the [CompositeEncoder] to be used to encode the given descriptor's elements in the expected order.
23+
* @param mapElementIndex maps the element index to the new reordered index (zero-based). If this mapper provides the same index for multiple elements, only the last one will be encoded as the previous ones will be overridden.
24+
*/
25+
@ExperimentalSerializationApi
26+
internal class ReorderingCompositeEncoder(
27+
structureDescriptor: SerialDescriptor,
28+
private val beginStructure: (SerialDescriptor) -> CompositeEncoder,
29+
private val mapElementIndex: (SerialDescriptor, Int) -> Int,
30+
) : CompositeEncoder {
31+
// Each time we encode a field, if the next expected schema field index is not the good one, it is buffered until it's the time to encode it
32+
private var bufferedCalls = Array<BufferedCall?>(structureDescriptor.elementsCount) { null }
33+
private var callShouldEncodeElementDefault = false
34+
35+
override val serializersModule: SerializersModule
36+
// No need to return a serializers module as it's not used during buffering
37+
get() = EmptySerializersModule()
38+
39+
private data class BufferedCall(
40+
val originalElementIndex: Int,
41+
val encoder: CompositeEncoder.() -> Unit,
42+
)
43+
44+
private inline fun bufferEncoding(
45+
descriptor: SerialDescriptor,
46+
index: Int,
47+
crossinline encoder: CompositeEncoder.() -> Unit
48+
) {
49+
if (callShouldEncodeElementDefault) {
50+
bufferedCalls[mapElementIndex(descriptor, index)] = BufferedCall(index) {
51+
if (shouldEncodeElementDefault(descriptor, index)) {
52+
encoder()
53+
}
54+
}
55+
} else {
56+
bufferedCalls[mapElementIndex(descriptor, index)] = BufferedCall(index) {
57+
encoder()
58+
}
59+
}
60+
callShouldEncodeElementDefault = false
61+
}
62+
63+
override fun endStructure(descriptor: SerialDescriptor) {
64+
encodeBufferedFields(descriptor)
65+
}
66+
67+
private fun encodeBufferedFields(descriptor: SerialDescriptor) {
68+
val classEncoder = beginStructure(descriptor)
69+
bufferedCalls.forEach { fieldToEncode ->
70+
fieldToEncode?.encoder?.invoke(classEncoder)
71+
}
72+
classEncoder.endStructure(descriptor)
73+
}
74+
75+
override fun encodeBooleanElement(descriptor: SerialDescriptor, index: Int, value: Boolean) {
76+
bufferEncoding(descriptor, index) { encodeBooleanElement(descriptor, index, value) }
77+
}
78+
79+
override fun encodeByteElement(descriptor: SerialDescriptor, index: Int, value: Byte) {
80+
bufferEncoding(descriptor, index) { encodeByteElement(descriptor, index, value) }
81+
}
82+
83+
override fun encodeCharElement(descriptor: SerialDescriptor, index: Int, value: Char) {
84+
bufferEncoding(descriptor, index) { encodeCharElement(descriptor, index, value) }
85+
}
86+
87+
override fun encodeDoubleElement(descriptor: SerialDescriptor, index: Int, value: Double) {
88+
bufferEncoding(descriptor, index) { encodeDoubleElement(descriptor, index, value) }
89+
}
90+
91+
override fun encodeFloatElement(descriptor: SerialDescriptor, index: Int, value: Float) {
92+
bufferEncoding(descriptor, index) { encodeFloatElement(descriptor, index, value) }
93+
}
94+
95+
override fun encodeIntElement(descriptor: SerialDescriptor, index: Int, value: Int) {
96+
bufferEncoding(descriptor, index) { encodeIntElement(descriptor, index, value) }
97+
}
98+
99+
override fun encodeLongElement(descriptor: SerialDescriptor, index: Int, value: Long) {
100+
bufferEncoding(descriptor, index) { encodeLongElement(descriptor, index, value) }
101+
}
102+
103+
override fun <T : Any> encodeNullableSerializableElement(
104+
descriptor: SerialDescriptor,
105+
index: Int,
106+
serializer: SerializationStrategy<T>,
107+
value: T?
108+
) {
109+
bufferEncoding(descriptor, index) { encodeNullableSerializableElement(descriptor, index, serializer, value) }
110+
}
111+
112+
override fun <T> encodeSerializableElement(
113+
descriptor: SerialDescriptor,
114+
index: Int,
115+
serializer: SerializationStrategy<T>,
116+
value: T
117+
) {
118+
bufferEncoding(descriptor, index) { encodeSerializableElement(descriptor, index, serializer, value) }
119+
}
120+
121+
override fun encodeShortElement(descriptor: SerialDescriptor, index: Int, value: Short) {
122+
bufferEncoding(descriptor, index) { encodeShortElement(descriptor, index, value) }
123+
}
124+
125+
override fun encodeStringElement(descriptor: SerialDescriptor, index: Int, value: String) {
126+
bufferEncoding(descriptor, index) { encodeStringElement(descriptor, index, value) }
127+
}
128+
129+
override fun encodeInlineElement(descriptor: SerialDescriptor, index: Int): Encoder {
130+
return BufferingInlineEncoder(descriptor, index)
131+
}
132+
133+
override fun shouldEncodeElementDefault(descriptor: SerialDescriptor, index: Int): Boolean {
134+
callShouldEncodeElementDefault = true
135+
return true
136+
}
137+
138+
private inner class BufferingInlineEncoder(
139+
private val descriptor: SerialDescriptor,
140+
private val elementIndex: Int,
141+
) : Encoder {
142+
private var nullable = false
143+
144+
override val serializersModule: SerializersModule
145+
get() = this@ReorderingCompositeEncoder.serializersModule
146+
147+
private fun bufferEncoding(encoder: Encoder.() -> Unit) {
148+
bufferEncoding(descriptor, elementIndex) {
149+
encodeInlineElement(descriptor, elementIndex).apply {
150+
if (nullable) {
151+
encodeNotNullMark()
152+
}
153+
encoder()
154+
}
155+
}
156+
}
157+
158+
override fun encodeNotNullMark() {
159+
nullable = true
160+
}
161+
162+
override fun <T : Any> encodeNullableSerializableValue(serializer: SerializationStrategy<T>, value: T?) {
163+
bufferEncoding { encodeNullableSerializableValue(serializer, value) }
164+
}
165+
166+
override fun <T> encodeSerializableValue(serializer: SerializationStrategy<T>, value: T) {
167+
bufferEncoding { encodeSerializableValue(serializer, value) }
168+
}
169+
170+
override fun encodeBoolean(value: Boolean) {
171+
bufferEncoding { encodeBoolean(value) }
172+
}
173+
174+
override fun encodeByte(value: Byte) {
175+
bufferEncoding { encodeByte(value) }
176+
}
177+
178+
override fun encodeChar(value: Char) {
179+
bufferEncoding { encodeChar(value) }
180+
}
181+
182+
override fun encodeDouble(value: Double) {
183+
bufferEncoding { encodeDouble(value) }
184+
}
185+
186+
override fun encodeEnum(enumDescriptor: SerialDescriptor, index: Int) {
187+
bufferEncoding { encodeEnum(enumDescriptor, index) }
188+
}
189+
190+
override fun encodeFloat(value: Float) {
191+
bufferEncoding { encodeFloat(value) }
192+
}
193+
194+
override fun encodeInt(value: Int) {
195+
bufferEncoding { encodeInt(value) }
196+
}
197+
198+
override fun encodeLong(value: Long) {
199+
bufferEncoding { encodeLong(value) }
200+
}
201+
202+
@ExperimentalSerializationApi
203+
override fun encodeNull() {
204+
bufferEncoding { encodeNull() }
205+
}
206+
207+
override fun encodeShort(value: Short) {
208+
bufferEncoding { encodeShort(value) }
209+
}
210+
211+
override fun encodeString(value: String) {
212+
bufferEncoding { encodeString(value) }
213+
}
214+
215+
override fun beginStructure(descriptor: SerialDescriptor): CompositeEncoder {
216+
invalidCall("beginStructure")
217+
}
218+
219+
override fun encodeInline(descriptor: SerialDescriptor): Encoder {
220+
invalidCall("encodeInline")
221+
}
222+
223+
private fun invalidCall(methodName: String): Nothing {
224+
// 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
225+
throw UnsupportedOperationException("$methodName should not be called when reordering fields, as it should be done by encodeSerializableValue or encodeNullableSerializableValue")
226+
}
227+
}
228+
}
229+
230+
/**
231+
* Encodes the [structureDescriptor] elements in a specific order provided by [elementIndexMapper].
232+
*
233+
* @param structureDescriptor descriptor of the structure being encoded and reordered
234+
* @param elementIndexMapper maps the element index to the new reordered index (zero-based). If this mapper provides the same index for multiple elements, only the last one will be encoded as the previous ones will be overridden.
235+
*/
236+
@ExperimentalSerializationApi
237+
public fun CompositeEncoder.reorderElements(
238+
structureDescriptor: SerialDescriptor,
239+
elementIndexMapper: (SerialDescriptor, Int) -> Int,
240+
): CompositeEncoder = ReorderingCompositeEncoder(structureDescriptor, { this }, elementIndexMapper)

0 commit comments

Comments
 (0)