1 /*
<lambda>null2  * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3  */
4 
5 @file:Suppress("unused")
6 
7 package kotlinx.serialization.json
8 
9 import kotlinx.serialization.*
10 import kotlinx.serialization.builtins.serializer
11 import kotlinx.serialization.descriptors.SerialDescriptor
12 import kotlinx.serialization.internal.InlinePrimitiveDescriptor
13 import kotlinx.serialization.json.internal.*
14 
15 /**
16  * Class representing single JSON element.
17  * Can be [JsonPrimitive], [JsonArray] or [JsonObject].
18  *
19  * [JsonElement.toString] properly prints JSON tree as valid JSON, taking into account quoted values and primitives.
20  * Whole hierarchy is serializable, but only when used with [Json] as [JsonElement] is purely JSON-specific structure
21  * which has a meaningful schemaless semantics only for JSON.
22  *
23  * The whole hierarchy is [serializable][Serializable] only by [Json] format.
24  */
25 @Serializable(JsonElementSerializer::class)
26 public sealed class JsonElement
27 
28 /**
29  * Class representing JSON primitive value.
30  * JSON primitives include numbers, strings, booleans and special null value [JsonNull].
31  */
32 @Serializable(JsonPrimitiveSerializer::class)
33 public sealed class JsonPrimitive : JsonElement() {
34 
35     /**
36      * Indicates whether the primitive was explicitly constructed from [String] and
37      * whether it should be serialized as one. E.g. `JsonPrimitive("42")` is represented
38      * by a string, while `JsonPrimitive(42)` is not.
39      * These primitives will be serialized as `42` and `"42"` respectively.
40      */
41     public abstract val isString: Boolean
42 
43     /**
44      * Content of given element without quotes. For [JsonNull] this methods returns `null`
45      */
46     public abstract val content: String
47 
48     public override fun toString(): String = content
49 }
50 
51 /** Creates a [JsonPrimitive] from the given boolean. */
JsonPrimitivenull52 public fun JsonPrimitive(value: Boolean?): JsonPrimitive {
53     if (value == null) return JsonNull
54     return JsonLiteral(value, isString = false)
55 }
56 
57 /** Creates a [JsonPrimitive] from the given number. */
JsonPrimitivenull58 public fun JsonPrimitive(value: Number?): JsonPrimitive {
59     if (value == null) return JsonNull
60     return JsonLiteral(value, isString = false)
61 }
62 
63 /**
64  * Creates a numeric [JsonPrimitive] from the given [UByte].
65  *
66  * The value will be encoded as a JSON number.
67  */
68 @ExperimentalSerializationApi
JsonPrimitivenull69 public fun JsonPrimitive(value: UByte): JsonPrimitive = JsonPrimitive(value.toULong())
70 
71 /**
72  * Creates a numeric [JsonPrimitive] from the given [UShort].
73  *
74  * The value will be encoded as a JSON number.
75  */
76 @ExperimentalSerializationApi
77 public fun JsonPrimitive(value: UShort): JsonPrimitive = JsonPrimitive(value.toULong())
78 
79 /**
80  * Creates a numeric [JsonPrimitive] from the given [UInt].
81  *
82  * The value will be encoded as a JSON number.
83  */
84 @ExperimentalSerializationApi
85 public fun JsonPrimitive(value: UInt): JsonPrimitive = JsonPrimitive(value.toULong())
86 
87 /**
88  * Creates a numeric [JsonPrimitive] from the given [ULong].
89  *
90  * The value will be encoded as a JSON number.
91  */
92 @SuppressAnimalSniffer // Long.toUnsignedString(long)
93 @ExperimentalSerializationApi
94 public fun JsonPrimitive(value: ULong): JsonPrimitive = JsonUnquotedLiteral(value.toString())
95 
96 /** Creates a [JsonPrimitive] from the given string. */
97 public fun JsonPrimitive(value: String?): JsonPrimitive {
98     if (value == null) return JsonNull
99     return JsonLiteral(value, isString = true)
100 }
101 
102 /** Creates [JsonNull]. */
103 @ExperimentalSerializationApi
104 @Suppress("FunctionName", "UNUSED_PARAMETER") // allows to call `JsonPrimitive(null)`
JsonPrimitivenull105 public fun JsonPrimitive(value: Nothing?): JsonNull = JsonNull
106 
107 /**
108  * Creates a [JsonPrimitive] from the given string, without surrounding it in quotes.
109  *
110  * This function is provided for encoding raw JSON values that cannot be encoded using the [JsonPrimitive] functions.
111  * For example,
112  *
113  * * precise numeric values (avoiding floating-point precision errors associated with [Double] and [Float]),
114  * * large numbers,
115  * * or complex JSON objects.
116  *
117  * Be aware that it is possible to create invalid JSON using this function.
118  *
119  * Creating a literal unquoted value of `null` (as in, `value == "null"`) is forbidden. If you want to create
120  * JSON null literal, use [JsonNull] object, otherwise, use [JsonPrimitive].
121  *
122  * @see JsonPrimitive is the preferred method for encoding JSON primitives.
123  * @throws JsonEncodingException if `value == "null"`
124  */
125 @ExperimentalSerializationApi
126 @Suppress("FunctionName")
127 public fun JsonUnquotedLiteral(value: String?): JsonPrimitive {
128     return when (value) {
129         null -> JsonNull
130         JsonNull.content -> throw JsonEncodingException("Creating a literal unquoted value of 'null' is forbidden. If you want to create JSON null literal, use JsonNull object, otherwise, use JsonPrimitive")
131         else -> JsonLiteral(value, isString = false, coerceToInlineType = jsonUnquotedLiteralDescriptor)
132     }
133 }
134 
135 /** Used as a marker to indicate during encoding that the [JsonEncoder] should use `encodeInline()` */
136 internal val jsonUnquotedLiteralDescriptor: SerialDescriptor =
137     InlinePrimitiveDescriptor("kotlinx.serialization.json.JsonUnquotedLiteral", String.serializer())
138 
139 
140 // JsonLiteral is deprecated for public use and no longer available. Please use JsonPrimitive instead
141 internal class JsonLiteral internal constructor(
142     body: Any,
143     public override val isString: Boolean,
144     internal val coerceToInlineType: SerialDescriptor? = null,
145 ) : JsonPrimitive() {
146     public override val content: String = body.toString()
147 
148     init {
149         if (coerceToInlineType != null) require(coerceToInlineType.isInline)
150     }
151 
toStringnull152     public override fun toString(): String =
153         if (isString) buildString { printQuoted(content) }
154         else content
155 
156     // Compare by `content` and `isString`, because body can be kotlin.Long=42 or kotlin.String="42"
equalsnull157     public override fun equals(other: Any?): Boolean {
158         if (this === other) return true
159         if (other == null || this::class != other::class) return false
160         other as JsonLiteral
161         if (isString != other.isString) return false
162         if (content != other.content) return false
163         return true
164     }
165 
166     @SuppressAnimalSniffer // Boolean.hashCode(boolean)
hashCodenull167     public override fun hashCode(): Int {
168         var result = isString.hashCode()
169         result = 31 * result + content.hashCode()
170         return result
171     }
172 }
173 
174 /**
175  * Class representing JSON `null` value
176  */
177 @Serializable(JsonNullSerializer::class)
178 public object JsonNull : JsonPrimitive() {
179     override val isString: Boolean get() = false
180     override val content: String = "null"
181 }
182 
183 /**
184  * Class representing JSON object, consisting of name-value pairs, where value is arbitrary [JsonElement]
185  *
186  * Since this class also implements [Map] interface, you can use
187  * traditional methods like [Map.get] or [Map.getValue] to obtain Json elements.
188  */
189 @Serializable(JsonObjectSerializer::class)
190 public class JsonObject(
191     private val content: Map<String, JsonElement>
<lambda>null192 ) : JsonElement(), Map<String, JsonElement> by content {
193     public override fun equals(other: Any?): Boolean = content == other
194     public override fun hashCode(): Int = content.hashCode()
195     public override fun toString(): String {
196         return content.entries.joinToString(
197             separator = ",",
198             prefix = "{",
199             postfix = "}",
200             transform = { (k, v) ->
201                 buildString {
202                     printQuoted(k)
203                     append(':')
204                     append(v)
205                 }
206             }
207         )
208     }
209 }
210 
211 /**
212  * Class representing JSON array, consisting of indexed values, where value is arbitrary [JsonElement]
213  *
214  * Since this class also implements [List] interface, you can use
215  * traditional methods like [List.get] or [List.getOrNull] to obtain Json elements.
216  */
217 @Serializable(JsonArraySerializer::class)
<lambda>null218 public class JsonArray(private val content: List<JsonElement>) : JsonElement(), List<JsonElement> by content {
219     public override fun equals(other: Any?): Boolean = content == other
220     public override fun hashCode(): Int = content.hashCode()
221     public override fun toString(): String = content.joinToString(prefix = "[", postfix = "]", separator = ",")
222 }
223 
224 /**
225  * Convenience method to get current element as [JsonPrimitive]
226  * @throws IllegalArgumentException if current element is not a [JsonPrimitive]
227  */
228 public val JsonElement.jsonPrimitive: JsonPrimitive
229     get() = this as? JsonPrimitive ?: error("JsonPrimitive")
230 
231 /**
232  * Convenience method to get current element as [JsonObject]
233  * @throws IllegalArgumentException if current element is not a [JsonObject]
234  */
235 public val JsonElement.jsonObject: JsonObject
236     get() = this as? JsonObject ?: error("JsonObject")
237 
238 /**
239  * Convenience method to get current element as [JsonArray]
240  * @throws IllegalArgumentException if current element is not a [JsonArray]
241  */
242 public val JsonElement.jsonArray: JsonArray
243     get() = this as? JsonArray ?: error("JsonArray")
244 
245 /**
246  * Convenience method to get current element as [JsonNull]
247  * @throws IllegalArgumentException if current element is not a [JsonNull]
248  */
249 public val JsonElement.jsonNull: JsonNull
250     get() = this as? JsonNull ?: error("JsonNull")
251 
252 /**
253  * Returns content of the current element as int
254  * @throws NumberFormatException if current element is not a valid representation of number
255  */
256 public val JsonPrimitive.int: Int
257     get() {
<lambda>null258         val result = mapExceptions { StringJsonLexer(content).consumeNumericLiteral() }
259         if (result !in Int.MIN_VALUE..Int.MAX_VALUE) throw NumberFormatException("$content is not an Int")
260         return result.toInt()
261     }
262 
263 /**
264  * Returns content of the current element as int or `null` if current element is not a valid representation of number
265  */
266 public val JsonPrimitive.intOrNull: Int?
267     get() {
<lambda>null268         val result = mapExceptionsToNull { StringJsonLexer(content).consumeNumericLiteral() } ?: return null
269         if (result !in Int.MIN_VALUE..Int.MAX_VALUE) return null
270         return result.toInt()
271     }
272 
273 /**
274  * Returns content of current element as long
275  * @throws NumberFormatException if current element is not a valid representation of number
276  */
<lambda>null277 public val JsonPrimitive.long: Long get() = mapExceptions { StringJsonLexer(content).consumeNumericLiteral() }
278 
279 /**
280  * Returns content of current element as long or `null` if current element is not a valid representation of number
281  */
282 public val JsonPrimitive.longOrNull: Long?
283     get() =
<lambda>null284         mapExceptionsToNull { StringJsonLexer(content).consumeNumericLiteral() }
285 
286 /**
287  * Returns content of current element as double
288  * @throws NumberFormatException if current element is not a valid representation of number
289  */
290 public val JsonPrimitive.double: Double get() = content.toDouble()
291 
292 /**
293  * Returns content of current element as double or `null` if current element is not a valid representation of number
294  */
295 public val JsonPrimitive.doubleOrNull: Double? get() = content.toDoubleOrNull()
296 
297 /**
298  * Returns content of current element as float
299  * @throws NumberFormatException if current element is not a valid representation of number
300  */
301 public val JsonPrimitive.float: Float get() = content.toFloat()
302 
303 /**
304  * Returns content of current element as float or `null` if current element is not a valid representation of number
305  */
306 public val JsonPrimitive.floatOrNull: Float? get() = content.toFloatOrNull()
307 
308 /**
309  * Returns content of current element as boolean
310  * @throws IllegalStateException if current element doesn't represent boolean
311  */
312 public val JsonPrimitive.boolean: Boolean
313     get() = content.toBooleanStrictOrNull() ?: throw IllegalStateException("$this does not represent a Boolean")
314 
315 /**
316  * Returns content of current element as boolean or `null` if current element is not a valid representation of boolean
317  */
318 public val JsonPrimitive.booleanOrNull: Boolean? get() = content.toBooleanStrictOrNull()
319 
320 /**
321  * Content of the given element without quotes or `null` if current element is [JsonNull]
322  */
323 public val JsonPrimitive.contentOrNull: String? get() = if (this is JsonNull) null else content
324 
JsonElementnull325 private fun JsonElement.error(element: String): Nothing =
326     throw IllegalArgumentException("Element ${this::class} is not a $element")
327 
328 private inline fun <T> mapExceptionsToNull(f: () -> T): T? {
329     return try {
330         f()
331     } catch (e: JsonDecodingException) {
332         null
333     }
334 }
335 
mapExceptionsnull336 private inline fun <T> mapExceptions(f: () -> T): T {
337     return try {
338         f()
339     } catch (e: JsonDecodingException) {
340         throw NumberFormatException(e.message)
341     }
342 }
343 
344 @PublishedApi
unexpectedJsonnull345 internal fun unexpectedJson(key: String, expected: String): Nothing =
346     throw IllegalArgumentException("Element $key is not a $expected")
347