• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3  */
4 @file:OptIn(ExperimentalSerializationApi::class)
5 
6 package kotlinx.serialization.json.internal
7 
8 import kotlinx.serialization.*
9 import kotlinx.serialization.descriptors.*
10 import kotlinx.serialization.encoding.*
11 import kotlinx.serialization.json.*
12 import kotlin.native.concurrent.*
13 
14 internal val JsonDeserializationNamesKey = DescriptorSchemaCache.Key<Map<String, Int>>()
15 
16 internal val JsonSerializationNamesKey = DescriptorSchemaCache.Key<Array<String>>()
17 
18 private fun SerialDescriptor.buildDeserializationNamesMap(json: Json): Map<String, Int> {
19     fun MutableMap<String, Int>.putOrThrow(name: String, index: Int) {
20         val entity = if (kind == SerialKind.ENUM) "enum value" else "property"
21         if (name in this) {
22             throw JsonException(
23                 "The suggested name '$name' for $entity ${getElementName(index)} is already one of the names for $entity " +
24                         "${getElementName(getValue(name))} in ${this@buildDeserializationNamesMap}"
25             )
26         }
27         this[name] = index
28     }
29 
30     val builder: MutableMap<String, Int> =
31         mutableMapOf() // can be not concurrent because it is only read after creation and safely published to concurrent map
32     val useLowercaseEnums = json.decodeCaseInsensitive(this)
33     val strategyForClasses = namingStrategy(json)
34     for (i in 0 until elementsCount) {
35         getElementAnnotations(i).filterIsInstance<JsonNames>().singleOrNull()?.names?.forEach { name ->
36             builder.putOrThrow(if (useLowercaseEnums) name.lowercase() else name, i)
37         }
38         val nameToPut = when {
39             // the branches do not intersect because useLowercase = true for enums only, and strategy != null for classes only.
40             useLowercaseEnums -> getElementName(i).lowercase()
41             strategyForClasses != null -> strategyForClasses.serialNameForJson(this, i, getElementName(i))
42             else -> null
43         }
44         nameToPut?.let { builder.putOrThrow(it, i) }
45     }
46     return builder.ifEmpty { emptyMap() }
47 }
48 
49 /**
50  * Contains strategy-mapped names and @JsonNames,
51  * so original names are not stored when strategy is `null`.
52  */
deserializationNamesMapnull53 internal fun Json.deserializationNamesMap(descriptor: SerialDescriptor): Map<String, Int> =
54     schemaCache.getOrPut(descriptor, JsonDeserializationNamesKey) { descriptor.buildDeserializationNamesMap(this) }
55 
serializationNamesIndicesnull56 internal fun SerialDescriptor.serializationNamesIndices(json: Json, strategy: JsonNamingStrategy): Array<String> =
57     json.schemaCache.getOrPut(this, JsonSerializationNamesKey) {
58         Array(elementsCount) { i ->
59             val baseName = getElementName(i)
60             strategy.serialNameForJson(this, i, baseName)
61         }
62     }
63 
getJsonElementNamenull64 internal fun SerialDescriptor.getJsonElementName(json: Json, index: Int): String {
65     val strategy = namingStrategy(json)
66     return if (strategy == null) getElementName(index) else serializationNamesIndices(json, strategy)[index]
67 }
68 
namingStrategynull69 internal fun SerialDescriptor.namingStrategy(json: Json) =
70     if (kind == StructureKind.CLASS) json.configuration.namingStrategy else null
71 
72 private fun SerialDescriptor.getJsonNameIndexSlowPath(json: Json, name: String): Int =
73     json.deserializationNamesMap(this)[name] ?: CompositeDecoder.UNKNOWN_NAME
74 
75 private fun Json.decodeCaseInsensitive(descriptor: SerialDescriptor) =
76     configuration.decodeEnumsCaseInsensitive && descriptor.kind == SerialKind.ENUM
77 
78 /**
79  * Serves same purpose as [SerialDescriptor.getElementIndex] but respects [JsonNames] annotation
80  * and [JsonConfiguration] settings.
81  */
82 @OptIn(ExperimentalSerializationApi::class)
83 internal fun SerialDescriptor.getJsonNameIndex(json: Json, name: String): Int {
84     if (json.decodeCaseInsensitive(this)) {
85         return getJsonNameIndexSlowPath(json, name.lowercase())
86     }
87 
88     val strategy = namingStrategy(json)
89     if (strategy != null) return getJsonNameIndexSlowPath(json, name)
90     val index = getElementIndex(name)
91     // Fast path, do not go through ConcurrentHashMap.get
92     // Note, it blocks ability to detect collisions between the primary name and alternate,
93     // but it eliminates a significant performance penalty (about -15% without this optimization)
94     if (index != CompositeDecoder.UNKNOWN_NAME) return index
95     if (!json.configuration.useAlternativeNames) return index
96     // Slow path
97     return getJsonNameIndexSlowPath(json, name)
98 }
99 
100 /**
101  * Throws on [CompositeDecoder.UNKNOWN_NAME]
102  */
103 @OptIn(ExperimentalSerializationApi::class)
getJsonNameIndexOrThrownull104 internal fun SerialDescriptor.getJsonNameIndexOrThrow(json: Json, name: String, suffix: String = ""): Int {
105     val index = getJsonNameIndex(json, name)
106     if (index == CompositeDecoder.UNKNOWN_NAME)
107         throw SerializationException("$serialName does not contain element with name '$name'$suffix")
108     return index
109 }
110 
111 @OptIn(ExperimentalSerializationApi::class)
tryCoerceValuenull112 internal inline fun Json.tryCoerceValue(
113     descriptor: SerialDescriptor,
114     index: Int,
115     peekNull: (consume: Boolean) -> Boolean,
116     peekString: () -> String?,
117     onEnumCoercing: () -> Unit = {}
118 ): Boolean {
119     if (!descriptor.isElementOptional(index)) return false
120     val elementDescriptor = descriptor.getElementDescriptor(index)
121     if (!elementDescriptor.isNullable && peekNull(true)) return true
122     if (elementDescriptor.kind == SerialKind.ENUM) {
123         if (elementDescriptor.isNullable && peekNull(false)) {
124             return false
125         }
126 
127         val enumValue = peekString()
128             ?: return false // if value is not a string, decodeEnum() will throw correct exception
129         val enumIndex = elementDescriptor.getJsonNameIndex(this, enumValue)
130         if (enumIndex == CompositeDecoder.UNKNOWN_NAME) {
131             onEnumCoercing()
132             return true
133         }
134     }
135     return false
136 }
137