Skip to content

Commit e067afd

Browse files
committed
feat: Add a way to reorder elements during encoding
1 parent 8c84a5b commit e067afd

File tree

3 files changed

+715
-1
lines changed

3 files changed

+715
-1
lines changed

Diff for: core/build.gradle.kts

+1-1
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

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
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 elements in a user-defined 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 the given [compositeEncoderDelegate].
19+
*
20+
* This encoder is stateful and not designed to be reused.
21+
*
22+
* @param compositeEncoderDelegate the [CompositeEncoder] to be used to encode the given descriptor's elements in the expected order.
23+
* @param encodedElementsCount The final number of elements to encode, which could be smaller than the original descriptor when [mapElementIndex] returns [SKIP_ELEMENT_INDEX] or when the index mapper has returned the same index twice.
24+
* @param mapElementIndex maps the element index to a new positional zero-based index.
25+
* The mapped index just helps to reorder the elements,
26+
* but the reordered `encode*Element` method calls will still pass the original element index.
27+
* If this mapper returns [SKIP_ELEMENT_INDEX] or -1, the element will be ignored and not encoded.
28+
* If this mapper provides the same index for multiple elements,
29+
* only the last one will be encoded as the previous ones will be overridden.
30+
*/
31+
@ExperimentalSerializationApi
32+
public class ReorderingCompositeEncoder(
33+
encodedElementsCount: Int,
34+
private val compositeEncoderDelegate: CompositeEncoder,
35+
private val mapElementIndex: (SerialDescriptor, Int) -> Int,
36+
) : CompositeEncoder {
37+
private val bufferedCalls = Array<BufferedCall?>(encodedElementsCount) { null }
38+
39+
public companion object {
40+
@ExperimentalSerializationApi
41+
public const val SKIP_ELEMENT_INDEX: Int = -1
42+
}
43+
44+
override val serializersModule: SerializersModule
45+
// No need to return a serializers module as it's not used during buffering
46+
get() = EmptySerializersModule()
47+
48+
private data class BufferedCall(
49+
val originalElementIndex: Int,
50+
val encoder: () -> Unit,
51+
)
52+
53+
private fun bufferEncoding(
54+
descriptor: SerialDescriptor,
55+
index: Int,
56+
encoder: () -> Unit
57+
) {
58+
val newIndex = mapElementIndex(descriptor, index)
59+
if (newIndex != SKIP_ELEMENT_INDEX) {
60+
bufferedCalls[newIndex] = BufferedCall(index, encoder)
61+
}
62+
}
63+
64+
override fun endStructure(descriptor: SerialDescriptor) {
65+
bufferedCalls.forEach { fieldToEncode ->
66+
// In case of skipped fields, overridden fields (mapped to same index) or too big [encodedElementsCount],
67+
// the fieldToEncode may be null as no element was encoded for that index
68+
fieldToEncode?.encoder?.invoke()
69+
}
70+
compositeEncoderDelegate.endStructure(descriptor)
71+
}
72+
73+
override fun encodeBooleanElement(descriptor: SerialDescriptor, index: Int, value: Boolean) {
74+
bufferEncoding(descriptor, index) {
75+
compositeEncoderDelegate.encodeBooleanElement(descriptor, index, value)
76+
}
77+
}
78+
79+
override fun encodeByteElement(descriptor: SerialDescriptor, index: Int, value: Byte) {
80+
bufferEncoding(descriptor, index) {
81+
compositeEncoderDelegate.encodeByteElement(descriptor, index, value)
82+
}
83+
}
84+
85+
override fun encodeCharElement(descriptor: SerialDescriptor, index: Int, value: Char) {
86+
bufferEncoding(descriptor, index) {
87+
compositeEncoderDelegate.encodeCharElement(descriptor, index, value)
88+
}
89+
}
90+
91+
override fun encodeDoubleElement(descriptor: SerialDescriptor, index: Int, value: Double) {
92+
bufferEncoding(descriptor, index) {
93+
compositeEncoderDelegate.encodeDoubleElement(descriptor, index, value)
94+
}
95+
}
96+
97+
override fun encodeFloatElement(descriptor: SerialDescriptor, index: Int, value: Float) {
98+
bufferEncoding(descriptor, index) {
99+
compositeEncoderDelegate.encodeFloatElement(descriptor, index, value)
100+
}
101+
}
102+
103+
override fun encodeIntElement(descriptor: SerialDescriptor, index: Int, value: Int) {
104+
bufferEncoding(descriptor, index) {
105+
compositeEncoderDelegate.encodeIntElement(descriptor, index, value)
106+
}
107+
}
108+
109+
override fun encodeLongElement(descriptor: SerialDescriptor, index: Int, value: Long) {
110+
bufferEncoding(descriptor, index) {
111+
compositeEncoderDelegate.encodeLongElement(descriptor, index, value)
112+
}
113+
}
114+
115+
override fun <T : Any> encodeNullableSerializableElement(
116+
descriptor: SerialDescriptor,
117+
index: Int,
118+
serializer: SerializationStrategy<T>,
119+
value: T?
120+
) {
121+
bufferEncoding(descriptor, index) {
122+
compositeEncoderDelegate.encodeNullableSerializableElement(descriptor, index, serializer, value)
123+
}
124+
}
125+
126+
override fun <T> encodeSerializableElement(
127+
descriptor: SerialDescriptor,
128+
index: Int,
129+
serializer: SerializationStrategy<T>,
130+
value: T
131+
) {
132+
bufferEncoding(descriptor, index) {
133+
compositeEncoderDelegate.encodeSerializableElement(descriptor, index, serializer, value)
134+
}
135+
}
136+
137+
override fun encodeShortElement(descriptor: SerialDescriptor, index: Int, value: Short) {
138+
bufferEncoding(descriptor, index) {
139+
compositeEncoderDelegate.encodeShortElement(descriptor, index, value)
140+
}
141+
}
142+
143+
override fun encodeStringElement(descriptor: SerialDescriptor, index: Int, value: String) {
144+
bufferEncoding(descriptor, index) {
145+
compositeEncoderDelegate.encodeStringElement(descriptor, index, value)
146+
}
147+
}
148+
149+
override fun encodeInlineElement(descriptor: SerialDescriptor, index: Int): Encoder {
150+
return BufferingInlineEncoder(descriptor, index)
151+
}
152+
153+
override fun shouldEncodeElementDefault(descriptor: SerialDescriptor, index: Int): Boolean {
154+
return compositeEncoderDelegate.shouldEncodeElementDefault(descriptor, index)
155+
}
156+
157+
private inner class BufferingInlineEncoder(
158+
private val descriptor: SerialDescriptor,
159+
private val elementIndex: Int,
160+
) : Encoder {
161+
private var encodeNotNullMarkCalled = false
162+
163+
override val serializersModule: SerializersModule
164+
get() = this@ReorderingCompositeEncoder.serializersModule
165+
166+
private fun bufferEncoding(encoder: Encoder.() -> Unit) {
167+
bufferEncoding(descriptor, elementIndex) {
168+
compositeEncoderDelegate.encodeInlineElement(descriptor, elementIndex).apply {
169+
if (encodeNotNullMarkCalled) {
170+
encodeNotNullMark()
171+
}
172+
encoder()
173+
}
174+
}
175+
}
176+
177+
override fun encodeNotNullMark() {
178+
encodeNotNullMarkCalled = true
179+
}
180+
181+
override fun <T : Any> encodeNullableSerializableValue(serializer: SerializationStrategy<T>, value: T?) {
182+
bufferEncoding { encodeNullableSerializableValue(serializer, value) }
183+
}
184+
185+
override fun <T> encodeSerializableValue(serializer: SerializationStrategy<T>, value: T) {
186+
bufferEncoding { encodeSerializableValue(serializer, value) }
187+
}
188+
189+
override fun encodeBoolean(value: Boolean) {
190+
bufferEncoding { encodeBoolean(value) }
191+
}
192+
193+
override fun encodeByte(value: Byte) {
194+
bufferEncoding { encodeByte(value) }
195+
}
196+
197+
override fun encodeChar(value: Char) {
198+
bufferEncoding { encodeChar(value) }
199+
}
200+
201+
override fun encodeDouble(value: Double) {
202+
bufferEncoding { encodeDouble(value) }
203+
}
204+
205+
override fun encodeEnum(enumDescriptor: SerialDescriptor, index: Int) {
206+
bufferEncoding { encodeEnum(enumDescriptor, index) }
207+
}
208+
209+
override fun encodeFloat(value: Float) {
210+
bufferEncoding { encodeFloat(value) }
211+
}
212+
213+
override fun encodeInt(value: Int) {
214+
bufferEncoding { encodeInt(value) }
215+
}
216+
217+
override fun encodeLong(value: Long) {
218+
bufferEncoding { encodeLong(value) }
219+
}
220+
221+
@ExperimentalSerializationApi
222+
override fun encodeNull() {
223+
bufferEncoding { encodeNull() }
224+
}
225+
226+
override fun encodeShort(value: Short) {
227+
bufferEncoding { encodeShort(value) }
228+
}
229+
230+
override fun encodeString(value: String) {
231+
bufferEncoding { encodeString(value) }
232+
}
233+
234+
override fun beginStructure(descriptor: SerialDescriptor): CompositeEncoder {
235+
unexpectedCall(::beginStructure.name)
236+
}
237+
238+
override fun encodeInline(descriptor: SerialDescriptor): Encoder {
239+
unexpectedCall(::encodeInline.name)
240+
}
241+
242+
private fun unexpectedCall(methodName: String): Nothing {
243+
// This method is normally called from within encodeSerializableValue or encodeNullableSerializableValue which is buffered, so we should never go here during buffering as it will be delegated to the concrete CompositeEncoder
244+
throw UnsupportedOperationException("Non-standard usage of ${CompositeEncoder::class.simpleName}: $methodName should be called from within encodeSerializableValue or encodeNullableSerializableValue")
245+
}
246+
}
247+
}

0 commit comments

Comments
 (0)