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 method returns a "null" string.
45 * [JsonPrimitive.contentOrNull] should be used for [JsonNull] to get a `null`.
46 */
47 public abstract val content: String
48
49 public override fun toString(): String = content
50 }
51
52 /** Creates a [JsonPrimitive] from the given boolean. */
JsonPrimitivenull53 public fun JsonPrimitive(value: Boolean?): JsonPrimitive {
54 if (value == null) return JsonNull
55 return JsonLiteral(value, isString = false)
56 }
57
58 /** Creates a [JsonPrimitive] from the given number. */
JsonPrimitivenull59 public fun JsonPrimitive(value: Number?): JsonPrimitive {
60 if (value == null) return JsonNull
61 return JsonLiteral(value, isString = false)
62 }
63
64 /**
65 * Creates a numeric [JsonPrimitive] from the given [UByte].
66 *
67 * The value will be encoded as a JSON number.
68 */
69 @ExperimentalSerializationApi
JsonPrimitivenull70 public fun JsonPrimitive(value: UByte): JsonPrimitive = JsonPrimitive(value.toULong())
71
72 /**
73 * Creates a numeric [JsonPrimitive] from the given [UShort].
74 *
75 * The value will be encoded as a JSON number.
76 */
77 @ExperimentalSerializationApi
78 public fun JsonPrimitive(value: UShort): JsonPrimitive = JsonPrimitive(value.toULong())
79
80 /**
81 * Creates a numeric [JsonPrimitive] from the given [UInt].
82 *
83 * The value will be encoded as a JSON number.
84 */
85 @ExperimentalSerializationApi
86 public fun JsonPrimitive(value: UInt): JsonPrimitive = JsonPrimitive(value.toULong())
87
88 /**
89 * Creates a numeric [JsonPrimitive] from the given [ULong].
90 *
91 * The value will be encoded as a JSON number.
92 */
93 @SuppressAnimalSniffer // Long.toUnsignedString(long)
94 @ExperimentalSerializationApi
95 public fun JsonPrimitive(value: ULong): JsonPrimitive = JsonUnquotedLiteral(value.toString())
96
97 /** Creates a [JsonPrimitive] from the given string. */
98 public fun JsonPrimitive(value: String?): JsonPrimitive {
99 if (value == null) return JsonNull
100 return JsonLiteral(value, isString = true)
101 }
102
103 /** Creates [JsonNull]. */
104 @ExperimentalSerializationApi
105 @Suppress("FunctionName", "UNUSED_PARAMETER") // allows to call `JsonPrimitive(null)`
JsonPrimitivenull106 public fun JsonPrimitive(value: Nothing?): JsonNull = JsonNull
107
108 /**
109 * Creates a [JsonPrimitive] from the given string, without surrounding it in quotes.
110 *
111 * This function is provided for encoding raw JSON values that cannot be encoded using the [JsonPrimitive] functions.
112 * For example,
113 *
114 * * precise numeric values (avoiding floating-point precision errors associated with [Double] and [Float]),
115 * * large numbers,
116 * * or complex JSON objects.
117 *
118 * Be aware that it is possible to create invalid JSON using this function.
119 *
120 * Creating a literal unquoted value of `null` (as in, `value == "null"`) is forbidden. If you want to create
121 * JSON null literal, use [JsonNull] object, otherwise, use [JsonPrimitive].
122 *
123 * @see JsonPrimitive is the preferred method for encoding JSON primitives.
124 * @throws JsonEncodingException if `value == "null"`
125 */
126 @ExperimentalSerializationApi
127 @Suppress("FunctionName")
128 public fun JsonUnquotedLiteral(value: String?): JsonPrimitive {
129 return when (value) {
130 null -> JsonNull
131 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")
132 else -> JsonLiteral(value, isString = false, coerceToInlineType = jsonUnquotedLiteralDescriptor)
133 }
134 }
135
136 /** Used as a marker to indicate during encoding that the [JsonEncoder] should use `encodeInline()` */
137 internal val jsonUnquotedLiteralDescriptor: SerialDescriptor =
138 InlinePrimitiveDescriptor("kotlinx.serialization.json.JsonUnquotedLiteral", String.serializer())
139
140
141 // JsonLiteral is deprecated for public use and no longer available. Please use JsonPrimitive instead
142 internal class JsonLiteral internal constructor(
143 body: Any,
144 public override val isString: Boolean,
145 internal val coerceToInlineType: SerialDescriptor? = null,
146 ) : JsonPrimitive() {
147 public override val content: String = body.toString()
148
149 init {
150 if (coerceToInlineType != null) require(coerceToInlineType.isInline)
151 }
152
toStringnull153 public override fun toString(): String =
154 if (isString) buildString { printQuoted(content) }
155 else content
156
157 // Compare by `content` and `isString`, because body can be kotlin.Long=42 or kotlin.String="42"
equalsnull158 public override fun equals(other: Any?): Boolean {
159 if (this === other) return true
160 if (other == null || this::class != other::class) return false
161 other as JsonLiteral
162 if (isString != other.isString) return false
163 if (content != other.content) return false
164 return true
165 }
166
167 @SuppressAnimalSniffer // Boolean.hashCode(boolean)
hashCodenull168 public override fun hashCode(): Int {
169 var result = isString.hashCode()
170 result = 31 * result + content.hashCode()
171 return result
172 }
173 }
174
175 /**
176 * Class representing JSON `null` value
177 */
178 @Serializable(JsonNullSerializer::class)
179 public object JsonNull : JsonPrimitive() {
180 override val isString: Boolean get() = false
181 override val content: String = "null"
182 }
183
184 /**
185 * Class representing JSON object, consisting of name-value pairs, where value is arbitrary [JsonElement]
186 *
187 * Since this class also implements [Map] interface, you can use
188 * traditional methods like [Map.get] or [Map.getValue] to obtain Json elements.
189 */
190 @Serializable(JsonObjectSerializer::class)
191 public class JsonObject(
192 private val content: Map<String, JsonElement>
<lambda>null193 ) : JsonElement(), Map<String, JsonElement> by content {
194 public override fun equals(other: Any?): Boolean = content == other
195 public override fun hashCode(): Int = content.hashCode()
196 public override fun toString(): String {
197 return content.entries.joinToString(
198 separator = ",",
199 prefix = "{",
200 postfix = "}",
201 transform = { (k, v) ->
202 buildString {
203 printQuoted(k)
204 append(':')
205 append(v)
206 }
207 }
208 )
209 }
210 }
211
212 /**
213 * Class representing JSON array, consisting of indexed values, where value is arbitrary [JsonElement]
214 *
215 * Since this class also implements [List] interface, you can use
216 * traditional methods like [List.get] or [List.getOrNull] to obtain Json elements.
217 */
218 @Serializable(JsonArraySerializer::class)
<lambda>null219 public class JsonArray(private val content: List<JsonElement>) : JsonElement(), List<JsonElement> by content {
220 public override fun equals(other: Any?): Boolean = content == other
221 public override fun hashCode(): Int = content.hashCode()
222 public override fun toString(): String = content.joinToString(prefix = "[", postfix = "]", separator = ",")
223 }
224
225 /**
226 * Convenience method to get current element as [JsonPrimitive]
227 * @throws IllegalArgumentException if current element is not a [JsonPrimitive]
228 */
229 public val JsonElement.jsonPrimitive: JsonPrimitive
230 get() = this as? JsonPrimitive ?: error("JsonPrimitive")
231
232 /**
233 * Convenience method to get current element as [JsonObject]
234 * @throws IllegalArgumentException if current element is not a [JsonObject]
235 */
236 public val JsonElement.jsonObject: JsonObject
237 get() = this as? JsonObject ?: error("JsonObject")
238
239 /**
240 * Convenience method to get current element as [JsonArray]
241 * @throws IllegalArgumentException if current element is not a [JsonArray]
242 */
243 public val JsonElement.jsonArray: JsonArray
244 get() = this as? JsonArray ?: error("JsonArray")
245
246 /**
247 * Convenience method to get current element as [JsonNull]
248 * @throws IllegalArgumentException if current element is not a [JsonNull]
249 */
250 public val JsonElement.jsonNull: JsonNull
251 get() = this as? JsonNull ?: error("JsonNull")
252
253 /**
254 * Returns content of the current element as int
255 * @throws NumberFormatException if current element is not a valid representation of number
256 */
257 public val JsonPrimitive.int: Int
258 get() {
<lambda>null259 val result = mapExceptions { StringJsonLexer(content).consumeNumericLiteral() }
260 if (result !in Int.MIN_VALUE..Int.MAX_VALUE) throw NumberFormatException("$content is not an Int")
261 return result.toInt()
262 }
263
264 /**
265 * Returns content of the current element as int or `null` if current element is not a valid representation of number
266 */
267 public val JsonPrimitive.intOrNull: Int?
268 get() {
<lambda>null269 val result = mapExceptionsToNull { StringJsonLexer(content).consumeNumericLiteral() } ?: return null
270 if (result !in Int.MIN_VALUE..Int.MAX_VALUE) return null
271 return result.toInt()
272 }
273
274 /**
275 * Returns content of current element as long
276 * @throws NumberFormatException if current element is not a valid representation of number
277 */
<lambda>null278 public val JsonPrimitive.long: Long get() = mapExceptions { StringJsonLexer(content).consumeNumericLiteral() }
279
280 /**
281 * Returns content of current element as long or `null` if current element is not a valid representation of number
282 */
283 public val JsonPrimitive.longOrNull: Long?
284 get() =
<lambda>null285 mapExceptionsToNull { StringJsonLexer(content).consumeNumericLiteral() }
286
287 /**
288 * Returns content of current element as double
289 * @throws NumberFormatException if current element is not a valid representation of number
290 */
291 public val JsonPrimitive.double: Double get() = content.toDouble()
292
293 /**
294 * Returns content of current element as double or `null` if current element is not a valid representation of number
295 */
296 public val JsonPrimitive.doubleOrNull: Double? get() = content.toDoubleOrNull()
297
298 /**
299 * Returns content of current element as float
300 * @throws NumberFormatException if current element is not a valid representation of number
301 */
302 public val JsonPrimitive.float: Float get() = content.toFloat()
303
304 /**
305 * Returns content of current element as float or `null` if current element is not a valid representation of number
306 */
307 public val JsonPrimitive.floatOrNull: Float? get() = content.toFloatOrNull()
308
309 /**
310 * Returns content of current element as boolean
311 * @throws IllegalStateException if current element doesn't represent boolean
312 */
313 public val JsonPrimitive.boolean: Boolean
314 get() = content.toBooleanStrictOrNull() ?: throw IllegalStateException("$this does not represent a Boolean")
315
316 /**
317 * Returns content of current element as boolean or `null` if current element is not a valid representation of boolean
318 */
319 public val JsonPrimitive.booleanOrNull: Boolean? get() = content.toBooleanStrictOrNull()
320
321 /**
322 * Content of the given element without quotes or `null` if current element is [JsonNull]
323 */
324 public val JsonPrimitive.contentOrNull: String? get() = if (this is JsonNull) null else content
325
JsonElementnull326 private fun JsonElement.error(element: String): Nothing =
327 throw IllegalArgumentException("Element ${this::class} is not a $element")
328
329 private inline fun <T> mapExceptionsToNull(f: () -> T): T? {
330 return try {
331 f()
332 } catch (e: JsonDecodingException) {
333 null
334 }
335 }
336
mapExceptionsnull337 private inline fun <T> mapExceptions(f: () -> T): T {
338 return try {
339 f()
340 } catch (e: JsonDecodingException) {
341 throw NumberFormatException(e.message)
342 }
343 }
344
345 @PublishedApi
unexpectedJsonnull346 internal fun unexpectedJson(key: String, expected: String): Nothing =
347 throw IllegalArgumentException("Element $key is not a $expected")
348