kotlinx.serialization icon indicating copy to clipboard operation
kotlinx.serialization copied to clipboard

Misleading IndexOutOfBoundsException when decoding JsonElement as primitive

Open rgmz opened this issue 2 years ago • 1 comments

Description

I encountered a confusing exception when writing JsonElement-related tests. I don't know how likely someone would be to stumble across this in the wild.

While it isn't a bug per se, the exception message could be clarified to point out the actual failure cause.

java.lang.IndexOutOfBoundsException: Index -1 out of bounds for length 0
	at java.base/jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:64)
	at java.base/jdk.internal.util.Preconditions.outOfBoundsCheckIndex(Preconditions.java:70)
	at java.base/jdk.internal.util.Preconditions.checkIndex(Preconditions.java:248)
	at java.base/java.util.Objects.checkIndex(Objects.java:372)
	at java.base/java.util.ArrayList.remove(ArrayList.java:536)
	at kotlinx.serialization.internal.TaggedDecoder.popTag(Tagged.kt:321)
	at kotlinx.serialization.internal.TaggedDecoder.decodeBoolean(Tagged.kt:223)
	at kotlinx.serialization.internal.BooleanSerializer.deserialize(Primitives.kt:86)
	at kotlinx.serialization.internal.BooleanSerializer.deserialize(Primitives.kt:82)
	at kotlinx.serialization.json.internal.PolymorphicKt.decodeSerializableValuePolymorphic(Polymorphic.kt:59)
	at kotlinx.serialization.json.internal.AbstractJsonTreeDecoder.decodeSerializableValue(TreeJsonDecoder.kt:51)
	at kotlinx.serialization.json.internal.TreeJsonDecoderKt.readJson(TreeJsonDecoder.kt:24)
	at kotlinx.serialization.json.Json.decodeFromJsonElement(Json.kt:119)

To Reproduce

Consider the following (contrived) code:

fun main() {
    val jsonString = JsonPrimitive("bar")
    val jsonObject = JsonObject(mapOf("foo" to jsonString))
    val jsonArray = JsonArray(listOf(jsonObject))

    listOf(jsonString, jsonObject, jsonArray).forEach { element ->
      runCatching { Json.decodeFromJsonElement<String>(element) }
         .fold(
            onSuccess = {  println("Value is: $value") },
            onFailure = {  println("Could not decode element: ${it.message}")  }
        )
    }
}

Attempting to decode JsonObject or JsonArray as a primitive (String, Boolean, Long, etc.) will yield the confusing error mentioned above.

https://github.com/Kotlin/kotlinx.serialization/blob/43d5f7841fc744b072a636b712e194081456b5ba/core/commonMain/src/kotlinx/serialization/internal/Tagged.kt#L320-L324

Expected behavior

Provide a meaningful exception, if possible. For example, attempting to decode a JsonObject or JsonArray as a JsonPrimitive yields a more friendly error:

kotlinx.serialization.json.internal.JsonDecodingException: Unexpected JSON element, expected JsonPrimitive, had class kotlinx.serialization.json.JsonObject
JSON input: {}
	at kotlinx.serialization.json.internal.JsonExceptionsKt.JsonDecodingException(JsonExceptions.kt:24)
	at kotlinx.serialization.json.internal.JsonExceptionsKt.JsonDecodingException(JsonExceptions.kt:32)
	at kotlinx.serialization.json.JsonPrimitiveSerializer.deserialize(JsonElementSerializers.kt:76)
	at kotlinx.serialization.json.JsonPrimitiveSerializer.deserialize(JsonElementSerializers.kt:59)
	at kotlinx.serialization.json.internal.PolymorphicKt.decodeSerializableValuePolymorphic(Polymorphic.kt:59)
	at kotlinx.serialization.json.internal.AbstractJsonTreeDecoder.decodeSerializableValue(TreeJsonDecoder.kt:51)
	at kotlinx.serialization.json.internal.TreeJsonDecoderKt.readJson(TreeJsonDecoder.kt:24)
	at kotlinx.serialization.json.Json.decodeFromJsonElement(Json.kt:119)

Environment

  • Kotlin version: 1.5.31
  • Library version: 1.3.0
  • Kotlin platforms: Jvm

rgmz avatar Nov 18 '21 20:11 rgmz

You saved me a lot of time, thanks @rgmz :) I stumbled across this and had no clue what was going on...

mainrs avatar Jun 03 '22 15:06 mainrs