• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3  */
4 
5 package kotlinx.serialization.json.internal
6 
7 import kotlinx.serialization.*
8 import kotlinx.serialization.descriptors.*
9 import kotlinx.serialization.encoding.*
10 import kotlinx.serialization.encoding.CompositeDecoder.Companion.DECODE_DONE
11 import kotlinx.serialization.encoding.CompositeDecoder.Companion.UNKNOWN_NAME
12 import kotlinx.serialization.internal.*
13 import kotlinx.serialization.json.*
14 import kotlinx.serialization.modules.*
15 import kotlin.jvm.*
16 
17 /**
18  * [JsonDecoder] which reads given JSON from [AbstractJsonLexer] field by field.
19  */
20 @OptIn(ExperimentalSerializationApi::class)
21 internal open class StreamingJsonDecoder(
22     final override val json: Json,
23     private val mode: WriteMode,
24     @JvmField internal val lexer: AbstractJsonLexer,
25     descriptor: SerialDescriptor,
26     discriminatorHolder: DiscriminatorHolder?
27 ) : JsonDecoder, ChunkedDecoder, AbstractDecoder() {
28 
29     // A mutable reference to the discriminator that have to be skipped when in optimistic phase
30     // of polymorphic serialization, see `decodeSerializableValue`
31     internal class DiscriminatorHolder(@JvmField var discriminatorToSkip: String?)
32 
trySkipnull33     private fun DiscriminatorHolder?.trySkip(unknownKey: String): Boolean {
34         if (this == null) return false
35         if (discriminatorToSkip == unknownKey) {
36             discriminatorToSkip = null
37             return true
38         }
39         return false
40     }
41 
42 
43     override val serializersModule: SerializersModule = json.serializersModule
44     private var currentIndex = -1
45     private var discriminatorHolder: DiscriminatorHolder? = discriminatorHolder
46     private val configuration = json.configuration
47 
48     private val elementMarker: JsonElementMarker? = if (configuration.explicitNulls) null else JsonElementMarker(descriptor)
49 
decodeJsonElementnull50     override fun decodeJsonElement(): JsonElement = JsonTreeReader(json.configuration, lexer).read()
51 
52     override fun <T> decodeSerializableValue(deserializer: DeserializationStrategy<T>): T {
53         try {
54             /*
55              * This is an optimized path over decodeSerializableValuePolymorphic(deserializer):
56              * dSVP reads the very next JSON tree into a memory as JsonElement and then runs TreeJsonDecoder over it
57              * in order to deal with an arbitrary order of keys, but with the price of additional memory pressure
58              * and CPU consumption.
59              * We would like to provide the best possible performance for data produced by kotlinx.serialization
60              * itself, for that we do the following optimistic optimization:
61              *
62              * 0) Remember current position in the string
63              * 1) Read the very next key of JSON structure
64              * 2) If it matches*  the discriminator key, read the value, remember current position
65              * 3) Return the value, recover an initial position
66              * (*) -- if it doesn't match, fallback to dSVP method.
67              */
68             if (deserializer !is AbstractPolymorphicSerializer<*> || json.configuration.useArrayPolymorphism) {
69                 return deserializer.deserialize(this)
70             }
71 
72             val discriminator = deserializer.descriptor.classDiscriminator(json)
73             val type = lexer.peekLeadingMatchingValue(discriminator, configuration.isLenient)
74                 ?: // Fallback to slow path if we haven't found discriminator on first try
75                 return decodeSerializableValuePolymorphic<T>(deserializer as DeserializationStrategy<T>)
76 
77             @Suppress("UNCHECKED_CAST")
78             val actualSerializer = try {
79                     deserializer.findPolymorphicSerializer(this, type)
80                 } catch (it: SerializationException) { // Wrap SerializationException into JsonDecodingException to preserve position, path, and input.
81                     // Split multiline message from private core function:
82                     // core/commonMain/src/kotlinx/serialization/internal/AbstractPolymorphicSerializer.kt:102
83                     val message = it.message!!.substringBefore('\n').removeSuffix(".")
84                     val hint = it.message!!.substringAfter('\n', missingDelimiterValue = "")
85                     lexer.fail(message, hint = hint)
86                 } as DeserializationStrategy<T>
87 
88             discriminatorHolder = DiscriminatorHolder(discriminator)
89             return actualSerializer.deserialize(this)
90 
91         } catch (e: MissingFieldException) {
92             // Add "at path" if and only if we've just caught an exception and it hasn't been augmented yet
93             if (e.message!!.contains("at path")) throw e
94             // NB: we could've use some additional flag marker or augment the stacktrace, but it seemed to be as too much of a burden
95             throw MissingFieldException(e.missingFields, e.message + " at path: " + lexer.path.getPath(), e)
96         }
97     }
98 
beginStructurenull99     override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder {
100         val newMode = json.switchMode(descriptor)
101         lexer.path.pushDescriptor(descriptor)
102         lexer.consumeNextToken(newMode.begin)
103         checkLeadingComma()
104         return when (newMode) {
105             // In fact resets current index that these modes rely on
106             WriteMode.LIST, WriteMode.MAP, WriteMode.POLY_OBJ -> StreamingJsonDecoder(
107                 json,
108                 newMode,
109                 lexer,
110                 descriptor,
111                 discriminatorHolder
112             )
113             else -> if (mode == newMode && json.configuration.explicitNulls) {
114                 this
115             } else {
116                 StreamingJsonDecoder(json, newMode, lexer, descriptor, discriminatorHolder)
117             }
118         }
119     }
120 
endStructurenull121     override fun endStructure(descriptor: SerialDescriptor) {
122         // If we're ignoring unknown keys, we have to skip all un-decoded elements,
123         // e.g. for object serialization. It can be the case when the descriptor does
124         // not have any elements and decodeElementIndex is not invoked at all
125         if (json.configuration.ignoreUnknownKeys && descriptor.elementsCount == 0) {
126             skipLeftoverElements(descriptor)
127         }
128         if (lexer.tryConsumeComma() && !json.configuration.allowTrailingComma) lexer.invalidTrailingComma("")
129         // First consume the object so we know it's correct
130         lexer.consumeNextToken(mode.end)
131         // Then cleanup the path
132         lexer.path.popDescriptor()
133     }
134 
skipLeftoverElementsnull135     private fun skipLeftoverElements(descriptor: SerialDescriptor) {
136         while (decodeElementIndex(descriptor) != DECODE_DONE) {
137             // Skip elements
138         }
139     }
140 
decodeNotNullMarknull141     override fun decodeNotNullMark(): Boolean {
142         return !(elementMarker?.isUnmarkedNull ?: false) && !lexer.tryConsumeNull()
143     }
144 
decodeNullnull145     override fun decodeNull(): Nothing? {
146         // Do nothing, null was consumed by `decodeNotNullMark`
147         return null
148     }
149 
checkLeadingCommanull150     private fun checkLeadingComma() {
151         if (lexer.peekNextToken() == TC_COMMA) {
152             lexer.fail("Unexpected leading comma")
153         }
154     }
155 
decodeSerializableElementnull156     override fun <T> decodeSerializableElement(
157         descriptor: SerialDescriptor,
158         index: Int,
159         deserializer: DeserializationStrategy<T>,
160         previousValue: T?
161     ): T {
162         val isMapKey = mode == WriteMode.MAP && index and 1 == 0
163         // Reset previous key
164         if (isMapKey) {
165             lexer.path.resetCurrentMapKey()
166         }
167         // Deserialize the key
168         val value = super.decodeSerializableElement(descriptor, index, deserializer, previousValue)
169         // Put the key to the path
170         if (isMapKey) {
171             lexer.path.updateCurrentMapKey(value)
172         }
173         return value
174     }
175 
decodeElementIndexnull176     override fun decodeElementIndex(descriptor: SerialDescriptor): Int {
177         val index = when (mode) {
178             WriteMode.OBJ -> decodeObjectIndex(descriptor)
179             WriteMode.MAP -> decodeMapIndex()
180             else -> decodeListIndex() // Both for LIST and default polymorphic
181         }
182         // The element of the next index that will be decoded
183         if (mode != WriteMode.MAP) {
184             lexer.path.updateDescriptorIndex(index)
185         }
186         return index
187     }
188 
decodeMapIndexnull189     private fun decodeMapIndex(): Int {
190         var hasComma = false
191         val decodingKey = currentIndex % 2 != 0
192         if (decodingKey) {
193             if (currentIndex != -1) {
194                 hasComma = lexer.tryConsumeComma()
195             }
196         } else {
197             lexer.consumeNextToken(COLON)
198         }
199 
200         return if (lexer.canConsumeValue()) {
201             if (decodingKey) {
202                 if (currentIndex == -1) lexer.require(!hasComma) { "Unexpected leading comma" }
203                 else lexer.require(hasComma) { "Expected comma after the key-value pair" }
204             }
205             ++currentIndex
206         } else {
207             if (hasComma && !json.configuration.allowTrailingComma) lexer.invalidTrailingComma()
208             CompositeDecoder.DECODE_DONE
209         }
210     }
211 
212     /*
213      * Checks whether JSON has `null` value for non-null property or unknown enum value for enum property
214      */
coerceInputValuenull215     private fun coerceInputValue(descriptor: SerialDescriptor, index: Int): Boolean = json.tryCoerceValue(
216         descriptor, index,
217         { lexer.tryConsumeNull(it) },
<lambda>null218         { lexer.peekString(configuration.isLenient) },
<lambda>null219         { lexer.consumeString() /* skip unknown enum string*/ }
220     )
221 
decodeObjectIndexnull222     private fun decodeObjectIndex(descriptor: SerialDescriptor): Int {
223         // hasComma checks are required to properly react on trailing commas
224         var hasComma = lexer.tryConsumeComma()
225         while (lexer.canConsumeValue()) { // TODO: consider merging comma consumption and this check
226             hasComma = false
227             val key = decodeStringKey()
228             lexer.consumeNextToken(COLON)
229             val index = descriptor.getJsonNameIndex(json, key)
230             val isUnknown = if (index != UNKNOWN_NAME) {
231                 if (configuration.coerceInputValues && coerceInputValue(descriptor, index)) {
232                     hasComma = lexer.tryConsumeComma()
233                     false // Known element, but coerced
234                 } else {
235                     elementMarker?.mark(index)
236                     return index // Known element without coercing, return it
237                 }
238             } else {
239                 true // unknown element
240             }
241 
242             if (isUnknown) { // slow-path for unknown keys handling
243                 hasComma = handleUnknown(key)
244             }
245         }
246         if (hasComma && !json.configuration.allowTrailingComma) lexer.invalidTrailingComma()
247 
248         return elementMarker?.nextUnmarkedIndex() ?: CompositeDecoder.DECODE_DONE
249     }
250 
handleUnknownnull251     private fun handleUnknown(key: String): Boolean {
252         if (configuration.ignoreUnknownKeys || discriminatorHolder.trySkip(key)) {
253             lexer.skipElement(configuration.isLenient)
254         } else {
255             // Here we cannot properly update json path indices
256             // as we do not have a proper SerialDescriptor in our hands
257             lexer.failOnUnknownKey(key)
258         }
259         return lexer.tryConsumeComma()
260     }
261 
decodeListIndexnull262     private fun decodeListIndex(): Int {
263         // Prohibit leading comma
264         val hasComma = lexer.tryConsumeComma()
265         return if (lexer.canConsumeValue()) {
266             if (currentIndex != -1 && !hasComma) lexer.fail("Expected end of the array or comma")
267             ++currentIndex
268         } else {
269             if (hasComma && !json.configuration.allowTrailingComma) lexer.invalidTrailingComma("array")
270             CompositeDecoder.DECODE_DONE
271         }
272     }
273 
274     /*
275      * The primitives are allowed to be quoted and unquoted
276      * to simplify map key parsing and integrations with third-party API.
277      */
decodeBooleannull278     override fun decodeBoolean(): Boolean {
279         return lexer.consumeBooleanLenient()
280     }
281 
decodeBytenull282     override fun decodeByte(): Byte {
283         val value = lexer.consumeNumericLiteral()
284         // Check for overflow
285         if (value != value.toByte().toLong()) lexer.fail("Failed to parse byte for input '$value'")
286         return value.toByte()
287     }
288 
decodeShortnull289     override fun decodeShort(): Short {
290         val value = lexer.consumeNumericLiteral()
291         // Check for overflow
292         if (value != value.toShort().toLong()) lexer.fail("Failed to parse short for input '$value'")
293         return value.toShort()
294     }
295 
decodeIntnull296     override fun decodeInt(): Int {
297         val value = lexer.consumeNumericLiteral()
298         // Check for overflow
299         if (value != value.toInt().toLong()) lexer.fail("Failed to parse int for input '$value'")
300         return value.toInt()
301     }
302 
decodeLongnull303     override fun decodeLong(): Long {
304         return lexer.consumeNumericLiteral()
305     }
306 
decodeFloatnull307     override fun decodeFloat(): Float {
308         val result = lexer.parseString("float") { toFloat() }
309         val specialFp = json.configuration.allowSpecialFloatingPointValues
310         if (specialFp || result.isFinite()) return result
311         lexer.throwInvalidFloatingPointDecoded(result)
312     }
313 
decodeDoublenull314     override fun decodeDouble(): Double {
315         val result = lexer.parseString("double") { toDouble() }
316         val specialFp = json.configuration.allowSpecialFloatingPointValues
317         if (specialFp || result.isFinite()) return result
318         lexer.throwInvalidFloatingPointDecoded(result)
319     }
320 
decodeCharnull321     override fun decodeChar(): Char {
322         val string = lexer.consumeStringLenient()
323         if (string.length != 1) lexer.fail("Expected single char, but got '$string'")
324         return string[0]
325     }
326 
decodeStringKeynull327     private fun decodeStringKey(): String {
328         return if (configuration.isLenient) {
329             lexer.consumeStringLenientNotNull()
330         } else {
331             lexer.consumeKeyString()
332         }
333     }
334 
decodeStringnull335     override fun decodeString(): String {
336         return if (configuration.isLenient) {
337             lexer.consumeStringLenientNotNull()
338         } else {
339             lexer.consumeString()
340         }
341     }
342 
decodeStringChunkednull343     override fun decodeStringChunked(consumeChunk: (chunk: String) -> Unit) {
344         lexer.consumeStringChunked(configuration.isLenient, consumeChunk)
345     }
346 
decodeInlinenull347     override fun decodeInline(descriptor: SerialDescriptor): Decoder =
348         if (descriptor.isUnsignedNumber) JsonDecoderForUnsignedTypes(lexer, json)
349         else super.decodeInline(descriptor)
350 
351     override fun decodeEnum(enumDescriptor: SerialDescriptor): Int {
352         return enumDescriptor.getJsonNameIndexOrThrow(json, decodeString(), " at path " + lexer.path.getPath())
353     }
354 }
355 
356 @JsonFriendModuleApi // used in json-tests
decodeStringToJsonTreenull357 public fun <T> decodeStringToJsonTree(
358     json: Json,
359     deserializer: DeserializationStrategy<T>,
360     source: String
361 ): JsonElement {
362     val lexer = StringJsonLexer(source)
363     val input = StreamingJsonDecoder(json, WriteMode.OBJ, lexer, deserializer.descriptor, null)
364     val tree = input.decodeJsonElement()
365     lexer.expectEof()
366     return tree
367 }
368 
369 @OptIn(ExperimentalSerializationApi::class)
370 internal class JsonDecoderForUnsignedTypes(
371     private val lexer: AbstractJsonLexer,
372     json: Json
373 ) : AbstractDecoder() {
374     override val serializersModule: SerializersModule = json.serializersModule
decodeElementIndexnull375     override fun decodeElementIndex(descriptor: SerialDescriptor): Int = error("unsupported")
376 
377     override fun decodeInt(): Int = lexer.parseString("UInt") { toUInt().toInt() }
<lambda>null378     override fun decodeLong(): Long = lexer.parseString("ULong") { toULong().toLong() }
<lambda>null379     override fun decodeByte(): Byte = lexer.parseString("UByte") { toUByte().toByte() }
<lambda>null380     override fun decodeShort(): Short = lexer.parseString("UShort") { toUShort().toShort() }
381 }
382 
parseStringnull383 private inline fun <T> AbstractJsonLexer.parseString(expectedType: String, block: String.() -> T): T {
384     val input = consumeStringLenient()
385     try {
386         return input.block()
387     } catch (e: IllegalArgumentException) {
388         fail("Failed to parse type '$expectedType' for input '$input'")
389     }
390 }
391