diff options
Diffstat (limited to 'formats/json/commonMain/src/kotlinx/serialization/json/JsonElement.kt')
-rw-r--r-- | formats/json/commonMain/src/kotlinx/serialization/json/JsonElement.kt | 135 |
1 files changed, 120 insertions, 15 deletions
diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/JsonElement.kt b/formats/json/commonMain/src/kotlinx/serialization/json/JsonElement.kt index 7d213304..74abf34a 100644 --- a/formats/json/commonMain/src/kotlinx/serialization/json/JsonElement.kt +++ b/formats/json/commonMain/src/kotlinx/serialization/json/JsonElement.kt @@ -1,5 +1,5 @@ /* - * Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ @file:Suppress("unused") @@ -7,6 +7,9 @@ package kotlinx.serialization.json import kotlinx.serialization.* +import kotlinx.serialization.builtins.serializer +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.internal.InlinePrimitiveDescriptor import kotlinx.serialization.json.internal.* /** @@ -45,37 +48,107 @@ public sealed class JsonPrimitive : JsonElement() { public override fun toString(): String = content } -/** - * Creates [JsonPrimitive] from the given boolean. - */ +/** Creates a [JsonPrimitive] from the given boolean. */ public fun JsonPrimitive(value: Boolean?): JsonPrimitive { if (value == null) return JsonNull return JsonLiteral(value, isString = false) } -/** - * Creates [JsonPrimitive] from the given number. - */ +/** Creates a [JsonPrimitive] from the given number. */ public fun JsonPrimitive(value: Number?): JsonPrimitive { if (value == null) return JsonNull return JsonLiteral(value, isString = false) } /** - * Creates [JsonPrimitive] from the given string. + * Creates a numeric [JsonPrimitive] from the given [UByte]. + * + * The value will be encoded as a JSON number. */ +@ExperimentalSerializationApi +public fun JsonPrimitive(value: UByte): JsonPrimitive = JsonPrimitive(value.toULong()) + +/** + * Creates a numeric [JsonPrimitive] from the given [UShort]. + * + * The value will be encoded as a JSON number. + */ +@ExperimentalSerializationApi +public fun JsonPrimitive(value: UShort): JsonPrimitive = JsonPrimitive(value.toULong()) + +/** + * Creates a numeric [JsonPrimitive] from the given [UInt]. + * + * The value will be encoded as a JSON number. + */ +@ExperimentalSerializationApi +public fun JsonPrimitive(value: UInt): JsonPrimitive = JsonPrimitive(value.toULong()) + +/** + * Creates a numeric [JsonPrimitive] from the given [ULong]. + * + * The value will be encoded as a JSON number. + */ +@SuppressAnimalSniffer // Long.toUnsignedString(long) +@ExperimentalSerializationApi +public fun JsonPrimitive(value: ULong): JsonPrimitive = JsonUnquotedLiteral(value.toString()) + +/** Creates a [JsonPrimitive] from the given string. */ public fun JsonPrimitive(value: String?): JsonPrimitive { if (value == null) return JsonNull return JsonLiteral(value, isString = true) } +/** Creates [JsonNull]. */ +@ExperimentalSerializationApi +@Suppress("FunctionName", "UNUSED_PARAMETER") // allows to call `JsonPrimitive(null)` +public fun JsonPrimitive(value: Nothing?): JsonNull = JsonNull + +/** + * Creates a [JsonPrimitive] from the given string, without surrounding it in quotes. + * + * This function is provided for encoding raw JSON values that cannot be encoded using the [JsonPrimitive] functions. + * For example, + * + * * precise numeric values (avoiding floating-point precision errors associated with [Double] and [Float]), + * * large numbers, + * * or complex JSON objects. + * + * Be aware that it is possible to create invalid JSON using this function. + * + * Creating a literal unquoted value of `null` (as in, `value == "null"`) is forbidden. If you want to create + * JSON null literal, use [JsonNull] object, otherwise, use [JsonPrimitive]. + * + * @see JsonPrimitive is the preferred method for encoding JSON primitives. + * @throws JsonEncodingException if `value == "null"` + */ +@ExperimentalSerializationApi +@Suppress("FunctionName") +public fun JsonUnquotedLiteral(value: String?): JsonPrimitive { + return when (value) { + null -> JsonNull + 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") + else -> JsonLiteral(value, isString = false, coerceToInlineType = jsonUnquotedLiteralDescriptor) + } +} + +/** Used as a marker to indicate during encoding that the [JsonEncoder] should use `encodeInline()` */ +internal val jsonUnquotedLiteralDescriptor: SerialDescriptor = + InlinePrimitiveDescriptor("kotlinx.serialization.json.JsonUnquotedLiteral", String.serializer()) + + // JsonLiteral is deprecated for public use and no longer available. Please use JsonPrimitive instead internal class JsonLiteral internal constructor( body: Any, - public override val isString: Boolean + public override val isString: Boolean, + internal val coerceToInlineType: SerialDescriptor? = null, ) : JsonPrimitive() { public override val content: String = body.toString() + init { + if (coerceToInlineType != null) require(coerceToInlineType.isInline) + } + public override fun toString(): String = if (isString) buildString { printQuoted(content) } else content @@ -90,6 +163,7 @@ internal class JsonLiteral internal constructor( return true } + @SuppressAnimalSniffer // Boolean.hashCode(boolean) public override fun hashCode(): Int { var result = isString.hashCode() result = 31 * result + content.hashCode() @@ -113,7 +187,9 @@ public object JsonNull : JsonPrimitive() { * traditional methods like [Map.get] or [Map.getValue] to obtain Json elements. */ @Serializable(JsonObjectSerializer::class) -public class JsonObject(private val content: Map<String, JsonElement>) : JsonElement(), Map<String, JsonElement> by content { +public class JsonObject( + private val content: Map<String, JsonElement> +) : JsonElement(), Map<String, JsonElement> by content { public override fun equals(other: Any?): Boolean = content == other public override fun hashCode(): Int = content.hashCode() public override fun toString(): String { @@ -177,23 +253,35 @@ public val JsonElement.jsonNull: JsonNull * Returns content of the current element as int * @throws NumberFormatException if current element is not a valid representation of number */ -public val JsonPrimitive.int: Int get() = content.toInt() +public val JsonPrimitive.int: Int + get() { + val result = mapExceptions { StringJsonLexer(content).consumeNumericLiteral() } + if (result !in Int.MIN_VALUE..Int.MAX_VALUE) throw NumberFormatException("$content is not an Int") + return result.toInt() + } /** * Returns content of the current element as int or `null` if current element is not a valid representation of number */ -public val JsonPrimitive.intOrNull: Int? get() = content.toIntOrNull() +public val JsonPrimitive.intOrNull: Int? + get() { + val result = mapExceptionsToNull { StringJsonLexer(content).consumeNumericLiteral() } ?: return null + if (result !in Int.MIN_VALUE..Int.MAX_VALUE) return null + return result.toInt() + } /** * Returns content of current element as long * @throws NumberFormatException if current element is not a valid representation of number */ -public val JsonPrimitive.long: Long get() = content.toLong() +public val JsonPrimitive.long: Long get() = mapExceptions { StringJsonLexer(content).consumeNumericLiteral() } /** * Returns content of current element as long or `null` if current element is not a valid representation of number */ -public val JsonPrimitive.longOrNull: Long? get() = content.toLongOrNull() +public val JsonPrimitive.longOrNull: Long? + get() = + mapExceptionsToNull { StringJsonLexer(content).consumeNumericLiteral() } /** * Returns content of current element as double @@ -221,7 +309,8 @@ public val JsonPrimitive.floatOrNull: Float? get() = content.toFloatOrNull() * Returns content of current element as boolean * @throws IllegalStateException if current element doesn't represent boolean */ -public val JsonPrimitive.boolean: Boolean get() = content.toBooleanStrictOrNull() ?: throw IllegalStateException("$this does not represent a Boolean") +public val JsonPrimitive.boolean: Boolean + get() = content.toBooleanStrictOrNull() ?: throw IllegalStateException("$this does not represent a Boolean") /** * Returns content of current element as boolean or `null` if current element is not a valid representation of boolean @@ -236,6 +325,22 @@ public val JsonPrimitive.contentOrNull: String? get() = if (this is JsonNull) nu private fun JsonElement.error(element: String): Nothing = throw IllegalArgumentException("Element ${this::class} is not a $element") +private inline fun <T> mapExceptionsToNull(f: () -> T): T? { + return try { + f() + } catch (e: JsonDecodingException) { + null + } +} + +private inline fun <T> mapExceptions(f: () -> T): T { + return try { + f() + } catch (e: JsonDecodingException) { + throw NumberFormatException(e.message) + } +} + @PublishedApi internal fun unexpectedJson(key: String, expected: String): Nothing = throw IllegalArgumentException("Element $key is not a $expected") |