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