summaryrefslogtreecommitdiff
path: root/formats/json/commonMain/src/kotlinx/serialization/json/JsonElement.kt
diff options
context:
space:
mode:
Diffstat (limited to 'formats/json/commonMain/src/kotlinx/serialization/json/JsonElement.kt')
-rw-r--r--formats/json/commonMain/src/kotlinx/serialization/json/JsonElement.kt135
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")