From 34fc72f36cfac2f4e8b2c69a82036430a91f480f Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Mon, 4 Jul 2022 18:30:20 +0200 Subject: [PATCH 1/4] Add general exception contract for KSerializer, improve documentation of SerializationExceptions to make it more KDoc-friendly Fixes #1875 --- .../src/kotlinx/serialization/KSerializer.kt | 19 +++++++ ...xception.kt => SerializationExceptions.kt} | 50 +++++++++---------- 2 files changed, 43 insertions(+), 26 deletions(-) rename core/commonMain/src/kotlinx/serialization/{SerializationException.kt => SerializationExceptions.kt} (63%) diff --git a/core/commonMain/src/kotlinx/serialization/KSerializer.kt b/core/commonMain/src/kotlinx/serialization/KSerializer.kt index 3b6c86972..946fe1f8e 100644 --- a/core/commonMain/src/kotlinx/serialization/KSerializer.kt +++ b/core/commonMain/src/kotlinx/serialization/KSerializer.kt @@ -51,6 +51,17 @@ import kotlinx.serialization.encoding.* * ``` * * Deserialization process is symmetric and uses [Decoder]. + * + * ### Exception types for KSerializer implementation + * + * Implementations of [serialize] and [deserialize] methods are allowed to throw + * any subtype of [IllegalArgumentException] in order to indicate serialization + * and deserialization errors. + * + * For serializer implementations, it is recommended to throw subclasses of [SerializationException] for + * any serialization-specific errors related to invalid or unsupported format of the data + * and [IllegalStateException] for errors during validation of the data. + * */ public interface KSerializer : SerializationStrategy, DeserializationStrategy { /** @@ -106,6 +117,10 @@ public interface SerializationStrategy { * // don't encode 'alwaysZero' property because we decided to do so * } // end of the structure * ``` + * + * @throws SerializationException in case of any serialization-specific error + * @throws IllegalArgumentException if the supplied input does not comply encoder's specification + * @see KSerializer for additional information about general contracts and exception specifics */ public fun serialize(encoder: Encoder, value: T) } @@ -171,6 +186,10 @@ public interface DeserializationStrategy { * return MyData(int, list, alwaysZero = 0L) * } * ``` + * + * @throws SerializationException in case of any deserialization-specific error + * @throws IllegalArgumentException if the decoded input is not a valid instance of [T] + * @see KSerializer for additional information about general contracts and exception specifics */ public fun deserialize(decoder: Decoder): T } diff --git a/core/commonMain/src/kotlinx/serialization/SerializationException.kt b/core/commonMain/src/kotlinx/serialization/SerializationExceptions.kt similarity index 63% rename from core/commonMain/src/kotlinx/serialization/SerializationException.kt rename to core/commonMain/src/kotlinx/serialization/SerializationExceptions.kt index 41631f50a..9b6905092 100644 --- a/core/commonMain/src/kotlinx/serialization/SerializationException.kt +++ b/core/commonMain/src/kotlinx/serialization/SerializationExceptions.kt @@ -6,36 +6,34 @@ package kotlinx.serialization /** * A generic exception indicating the problem in serialization or deserialization process. - * This is a generic exception type that can be thrown during the problem at any stage of the serialization, - * including encoding, decoding, serialization, deserialization. + * + * This is a generic exception type that can be thrown during problems at any stage of the serialization, + * including encoding, decoding, serialization, deserialization, and validation. * [SerialFormat] implementors should throw subclasses of this exception at any unexpected event, * whether it is a malformed input or unsupported class layout. + * + * [SerializationException] is a subclass of [IllegalArgumentException] for the sake of consistency and user-defined validation: + * Any serialization exception is triggered by the illegal input, whether + * it is a serializer that does not support specific structure or an invalid input. + * + * It is also an established pattern to validate input in user's classes in the following manner: + * ``` + * @Serializable + * class Foo(...) { + * init { + * required(age > 0) { ... } + * require(name.isNotBlank()) { ... } + * } + * } + * ``` + * While clearly being serialization error (when compromised data was deserialized), + * Kotlin way is to throw `IllegalArgumentException` here instead of using library-specific `SerializationException`. + * + * For general "catch-all" patterns around deserialization of potentially + * untrusted/invalid/corrupted data it is recommended to catch `IllegalArgumentException` type + * to avoid catching irrelevant to serializaton errors such as `OutOfMemoryError` or domain-specific ones. */ public open class SerializationException : IllegalArgumentException { - /* - * Rationale behind making it IllegalArgumentException: - * Any serialization exception is triggered by the illegal argument, whether - * it is a serializer that does not support specific structure or an invalid input. - * Making it IAE just aligns the implementation with this fact. - * - * Another point is input validation. The simplest way to validate - * deserialized data is `require` in `init` block: - * ``` - * @Serializable class Foo(...) { - * init { - * required(age > 0) { ... } - * require(name.isNotBlank()) { ... } - * } - * } - * ``` - * While clearly being serialization error (when compromised data was deserialized), - * Kotlin way is to throw IAE here instead of using library-specific SerializationException. - * - * Also, any production-grade system has a general try-catch around deserialization of potentially - * untrusted/invalid/corrupted data with the corresponding logging, error reporting and diagnostic. - * Such handling should catch some subtype of exception (e.g. it's unlikely that catching OOM is desirable). - * Taking it into account, it becomes clear that SE should be subtype of IAE. - */ /** * Creates an instance of [SerializationException] without any details. From 69bbbeb8434b2c79a648d08929664fe3fc24e3a8 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Mon, 4 Jul 2022 18:40:26 +0200 Subject: [PATCH 2/4] Document exception types on encode/decode methods in SerialFormat --- .../src/kotlinx/serialization/KSerializer.kt | 2 +- .../src/kotlinx/serialization/SerialFormat.kt | 40 ++++++++++++++----- 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/core/commonMain/src/kotlinx/serialization/KSerializer.kt b/core/commonMain/src/kotlinx/serialization/KSerializer.kt index 946fe1f8e..2d9def188 100644 --- a/core/commonMain/src/kotlinx/serialization/KSerializer.kt +++ b/core/commonMain/src/kotlinx/serialization/KSerializer.kt @@ -52,7 +52,7 @@ import kotlinx.serialization.encoding.* * * Deserialization process is symmetric and uses [Decoder]. * - * ### Exception types for KSerializer implementation + * ### Exception types for `KSerializer` implementation * * Implementations of [serialize] and [deserialize] methods are allowed to throw * any subtype of [IllegalArgumentException] in order to indicate serialization diff --git a/core/commonMain/src/kotlinx/serialization/SerialFormat.kt b/core/commonMain/src/kotlinx/serialization/SerialFormat.kt index e4801a0a5..20c60e63f 100644 --- a/core/commonMain/src/kotlinx/serialization/SerialFormat.kt +++ b/core/commonMain/src/kotlinx/serialization/SerialFormat.kt @@ -19,6 +19,16 @@ import kotlinx.serialization.modules.* * Typically, formats have their specific [Encoder] and [Decoder] implementations * as private classes and do not expose them. * + * ### Exception types for `SerialFormat` implementation + * + * Methods responsible for format-specific encoding and decoding are allowed to throw + * any subtype of [IllegalArgumentException] in order to indicate serialization + * and deserialization errors. It is recommended to throw subtypes of [SerializationException] + * for encoder and decoder specific errors and [IllegalArgumentException] for input + * and output validation-specific errors. + * + * For formats + * * ### Not stable for inheritance * * `SerialFormat` interface is not stable for inheritance in 3rd party libraries, as new methods @@ -49,11 +59,17 @@ public interface BinaryFormat : SerialFormat { /** * Serializes and encodes the given [value] to byte array using the given [serializer]. + * + * @throws SerializationException in case of any encoding-specific error + * @throws IllegalArgumentException if the encoded input does not comply format's specification */ public fun encodeToByteArray(serializer: SerializationStrategy, value: T): ByteArray /** - * Decodes and deserializes the given [byte array][bytes] to the value of type [T] using the given [deserializer] + * Decodes and deserializes the given [byte array][bytes] to the value of type [T] using the given [deserializer]. + * + * @throws SerializationException in case of any decoding-specific error + * @throws IllegalArgumentException if the decoded input is not a valid instance of [T] */ public fun decodeFromByteArray(deserializer: DeserializationStrategy, bytes: ByteArray): T } @@ -72,27 +88,37 @@ public interface StringFormat : SerialFormat { /** * Serializes and encodes the given [value] to string using the given [serializer]. + * + * @throws SerializationException in case of any encoding-specific error + * @throws IllegalArgumentException if the encoded input does not comply format's specification */ public fun encodeToString(serializer: SerializationStrategy, value: T): String /** - * Decodes and deserializes the given [string] to the value of type [T] using the given [deserializer] + * Decodes and deserializes the given [string] to the value of type [T] using the given [deserializer]. + * + * @throws SerializationException in case of any decoding-specific error + * @throws IllegalArgumentException if the decoded input is not a valid instance of [T] */ public fun decodeFromString(deserializer: DeserializationStrategy, string: String): T } /** * Serializes and encodes the given [value] to string using serializer retrieved from the reified type parameter. + * + * @throws SerializationException in case of any encoding-specific error + * @throws IllegalArgumentException if the encoded input does not comply format's specification */ -@OptIn(ExperimentalSerializationApi::class) public inline fun StringFormat.encodeToString(value: T): String = encodeToString(serializersModule.serializer(), value) /** * Decodes and deserializes the given [string] to the value of type [T] using deserializer * retrieved from the reified type parameter. + * + * @throws SerializationException in case of any decoding-specific error + * @throws IllegalArgumentException if the decoded input is not a valid instance of [T] */ -@OptIn(ExperimentalSerializationApi::class) public inline fun StringFormat.decodeFromString(string: String): T = decodeFromString(serializersModule.serializer(), string) @@ -105,7 +131,6 @@ public inline fun StringFormat.decodeFromString(string: String): T = * only applies transformation to the resulting array. It is recommended to use for debugging and * testing purposes. */ -@OptIn(ExperimentalSerializationApi::class) public fun BinaryFormat.encodeToHexString(serializer: SerializationStrategy, value: T): String = InternalHexConverter.printHexBinary(encodeToByteArray(serializer, value), lowerCase = true) @@ -115,7 +140,6 @@ public fun BinaryFormat.encodeToHexString(serializer: SerializationStrategy< * * This method is a counterpart to [encodeToHexString] */ -@OptIn(ExperimentalSerializationApi::class) public fun BinaryFormat.decodeFromHexString(deserializer: DeserializationStrategy, hex: String): T = decodeFromByteArray(deserializer, InternalHexConverter.parseHexBinary(hex)) @@ -127,7 +151,6 @@ public fun BinaryFormat.decodeFromHexString(deserializer: DeserializationStr * only applies transformation to the resulting array. It is recommended to use for debugging and * testing purposes. */ -@OptIn(ExperimentalSerializationApi::class) public inline fun BinaryFormat.encodeToHexString(value: T): String = encodeToHexString(serializersModule.serializer(), value) @@ -137,7 +160,6 @@ public inline fun BinaryFormat.encodeToHexString(value: T): String = * * This method is a counterpart to [encodeToHexString] */ -@OptIn(ExperimentalSerializationApi::class) public inline fun BinaryFormat.decodeFromHexString(hex: String): T = decodeFromHexString(serializersModule.serializer(), hex) @@ -145,7 +167,6 @@ public inline fun BinaryFormat.decodeFromHexString(hex: String): T = * Serializes and encodes the given [value] to byte array using serializer * retrieved from the reified type parameter. */ -@OptIn(ExperimentalSerializationApi::class) public inline fun BinaryFormat.encodeToByteArray(value: T): ByteArray = encodeToByteArray(serializersModule.serializer(), value) @@ -153,6 +174,5 @@ public inline fun BinaryFormat.encodeToByteArray(value: T): ByteArra * Decodes and deserializes the given [byte array][bytes] to the value of type [T] using deserializer * retrieved from the reified type parameter. */ -@OptIn(ExperimentalSerializationApi::class) public inline fun BinaryFormat.decodeFromByteArray(bytes: ByteArray): T = decodeFromByteArray(serializersModule.serializer(), bytes) From 2ca50f18c8b1b371874b8e66475cbb0e34ba4a8b Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Mon, 4 Jul 2022 18:46:25 +0200 Subject: [PATCH 3/4] Document exception types on Json format --- .../commonMain/src/kotlinx/serialization/json/Json.kt | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/Json.kt b/formats/json/commonMain/src/kotlinx/serialization/json/Json.kt index 048715114..00fbde8de 100644 --- a/formats/json/commonMain/src/kotlinx/serialization/json/Json.kt +++ b/formats/json/commonMain/src/kotlinx/serialization/json/Json.kt @@ -86,7 +86,8 @@ public sealed class Json( /** * Deserializes the given JSON [string] into a value of type [T] using the given [deserializer]. * - * @throws [SerializationException] if the given JSON string cannot be deserialized to the value of type [T]. + * @throws [SerializationException] if the given JSON string is not a valid JSON input for the type [T] + * @throws [IllegalArgumentException] if the decoded input cannot be represented as a valid instance of type [T] */ public final override fun decodeFromString(deserializer: DeserializationStrategy, string: String): T { val lexer = StringJsonLexer(string) @@ -98,7 +99,7 @@ public sealed class Json( /** * Serializes the given [value] into an equivalent [JsonElement] using the given [serializer] * - * @throws [SerializationException] if the given value cannot be serialized. + * @throws [SerializationException] if the given value cannot be serialized to JSON */ public fun encodeToJsonElement(serializer: SerializationStrategy, value: T): JsonElement { return writeJson(value, serializer) @@ -107,7 +108,8 @@ public sealed class Json( /** * Deserializes the given [element] into a value of type [T] using the given [deserializer]. * - * @throws [SerializationException] if the given JSON string cannot be deserialized to the value of type [T]. + * @throws [SerializationException] if the given JSON element is not a valid JSON input for the type [T] + * @throws [IllegalArgumentException] if the decoded input cannot be represented as a valid instance of type [T] */ public fun decodeFromJsonElement(deserializer: DeserializationStrategy, element: JsonElement): T { return readJson(element, deserializer) @@ -116,7 +118,7 @@ public sealed class Json( /** * Deserializes the given JSON [string] into a corresponding [JsonElement] representation. * - * @throws [SerializationException] if the given JSON string is malformed and cannot be deserialized + * @throws [SerializationException] if the given string is not a valid JSON */ public fun parseToJsonElement(string: String): JsonElement { return decodeFromString(JsonElementSerializer, string) @@ -180,7 +182,6 @@ public enum class DecodeSequenceMode { /** * Creates an instance of [Json] configured from the optionally given [Json instance][from] and adjusted with [builderAction]. */ -@OptIn(ExperimentalSerializationApi::class) public fun Json(from: Json = Json.Default, builderAction: JsonBuilder.() -> Unit): Json { val builder = JsonBuilder(from) builder.builderAction() From 001e28163d3f5583d39f3c90fa45873f06f2b0ed Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Tue, 5 Jul 2022 17:36:37 +0200 Subject: [PATCH 4/4] More exception specs --- .../src/kotlinx/serialization/SerialFormat.kt | 22 +++++++++++++++++-- .../src/kotlinx/serialization/json/Json.kt | 3 ++- .../json/internal/JsonStreams.kt | 11 +++++----- .../kotlinx/serialization/json/JvmStreams.kt | 16 +++++++++----- 4 files changed, 38 insertions(+), 14 deletions(-) diff --git a/core/commonMain/src/kotlinx/serialization/SerialFormat.kt b/core/commonMain/src/kotlinx/serialization/SerialFormat.kt index 20c60e63f..11234b2a5 100644 --- a/core/commonMain/src/kotlinx/serialization/SerialFormat.kt +++ b/core/commonMain/src/kotlinx/serialization/SerialFormat.kt @@ -130,6 +130,9 @@ public inline fun StringFormat.decodeFromString(string: String): T = * Hex representation does not interfere with serialization and encoding process of the format and * only applies transformation to the resulting array. It is recommended to use for debugging and * testing purposes. + * + * @throws SerializationException in case of any encoding-specific error + * @throws IllegalArgumentException if the encoded input does not comply format's specification */ public fun BinaryFormat.encodeToHexString(serializer: SerializationStrategy, value: T): String = InternalHexConverter.printHexBinary(encodeToByteArray(serializer, value), lowerCase = true) @@ -138,7 +141,10 @@ public fun BinaryFormat.encodeToHexString(serializer: SerializationStrategy< * Decodes byte array from the given [hex] string and the decodes and deserializes it * to the value of type [T], delegating it to the [BinaryFormat]. * - * This method is a counterpart to [encodeToHexString] + * This method is a counterpart to [encodeToHexString]. + * + * @throws SerializationException in case of any decoding-specific error + * @throws IllegalArgumentException if the decoded input is not a valid instance of [T] */ public fun BinaryFormat.decodeFromHexString(deserializer: DeserializationStrategy, hex: String): T = decodeFromByteArray(deserializer, InternalHexConverter.parseHexBinary(hex)) @@ -150,6 +156,9 @@ public fun BinaryFormat.decodeFromHexString(deserializer: DeserializationStr * Hex representation does not interfere with serialization and encoding process of the format and * only applies transformation to the resulting array. It is recommended to use for debugging and * testing purposes. + * + * @throws SerializationException in case of any encoding-specific error + * @throws IllegalArgumentException if the encoded input does not comply format's specification */ public inline fun BinaryFormat.encodeToHexString(value: T): String = encodeToHexString(serializersModule.serializer(), value) @@ -158,7 +167,10 @@ public inline fun BinaryFormat.encodeToHexString(value: T): String = * Decodes byte array from the given [hex] string and the decodes and deserializes it * to the value of type [T], delegating it to the [BinaryFormat]. * - * This method is a counterpart to [encodeToHexString] + * This method is a counterpart to [encodeToHexString]. + * + * @throws SerializationException in case of any decoding-specific error + * @throws IllegalArgumentException if the decoded input is not a valid instance of [T] */ public inline fun BinaryFormat.decodeFromHexString(hex: String): T = decodeFromHexString(serializersModule.serializer(), hex) @@ -166,6 +178,9 @@ public inline fun BinaryFormat.decodeFromHexString(hex: String): T = /** * Serializes and encodes the given [value] to byte array using serializer * retrieved from the reified type parameter. + * + * @throws SerializationException in case of any encoding-specific error + * @throws IllegalArgumentException if the encoded input does not comply format's specification */ public inline fun BinaryFormat.encodeToByteArray(value: T): ByteArray = encodeToByteArray(serializersModule.serializer(), value) @@ -173,6 +188,9 @@ public inline fun BinaryFormat.encodeToByteArray(value: T): ByteArra /** * Decodes and deserializes the given [byte array][bytes] to the value of type [T] using deserializer * retrieved from the reified type parameter. + * + * @throws SerializationException in case of any decoding-specific error + * @throws IllegalArgumentException if the decoded input is not a valid instance of [T] */ public inline fun BinaryFormat.decodeFromByteArray(bytes: ByteArray): T = decodeFromByteArray(serializersModule.serializer(), bytes) diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/Json.kt b/formats/json/commonMain/src/kotlinx/serialization/json/Json.kt index 00fbde8de..339209392 100644 --- a/formats/json/commonMain/src/kotlinx/serialization/json/Json.kt +++ b/formats/json/commonMain/src/kotlinx/serialization/json/Json.kt @@ -203,7 +203,8 @@ public inline fun Json.encodeToJsonElement(value: T): JsonElement { * Deserializes the given [json] element into a value of type [T] using a deserializer retrieved * from reified type parameter. * - * @throws [SerializationException] if the given JSON string is malformed or cannot be deserialized to the value of type [T]. + * @throws [SerializationException] if the given JSON element is not a valid JSON input for the type [T] + * @throws [IllegalArgumentException] if the decoded input cannot be represented as a valid instance of type [T] */ public inline fun Json.decodeFromJsonElement(json: JsonElement): T = decodeFromJsonElement(serializersModule.serializer(), json) diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonStreams.kt b/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonStreams.kt index 757032495..0cf2b5d7f 100644 --- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonStreams.kt +++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonStreams.kt @@ -4,25 +4,23 @@ import kotlinx.serialization.* import kotlinx.serialization.json.DecodeSequenceMode import kotlinx.serialization.json.Json - - +/** @suppress */ @InternalSerializationApi public interface JsonWriter { public fun writeLong(value: Long) public fun writeChar(char: Char) - public fun write(text: String) - public fun writeQuoted(text: String) - public fun release() } +/** @suppress */ @InternalSerializationApi public interface SerialReader { public fun read(buffer: CharArray, bufferOffset: Int, count: Int): Int } +/** @suppress */ @InternalSerializationApi public fun Json.encodeByWriter(writer: JsonWriter, serializer: SerializationStrategy, value: T) { val encoder = StreamingJsonEncoder( @@ -33,6 +31,7 @@ public fun Json.encodeByWriter(writer: JsonWriter, serializer: Serialization encoder.encodeSerializableValue(serializer, value) } +/** @suppress */ @InternalSerializationApi public fun Json.decodeByReader( deserializer: DeserializationStrategy, @@ -45,6 +44,7 @@ public fun Json.decodeByReader( return result } +/** @suppress */ @InternalSerializationApi @ExperimentalSerializationApi public fun Json.decodeToSequenceByReader( @@ -57,6 +57,7 @@ public fun Json.decodeToSequenceByReader( return Sequence { iter }.constrainOnce() } +/** @suppress */ @InternalSerializationApi @ExperimentalSerializationApi public inline fun Json.decodeToSequenceByReader( diff --git a/formats/json/jvmMain/src/kotlinx/serialization/json/JvmStreams.kt b/formats/json/jvmMain/src/kotlinx/serialization/json/JvmStreams.kt index 04d8149b8..81bfc5631 100644 --- a/formats/json/jvmMain/src/kotlinx/serialization/json/JvmStreams.kt +++ b/formats/json/jvmMain/src/kotlinx/serialization/json/JvmStreams.kt @@ -12,7 +12,7 @@ import java.io.* * Serializes the [value] with [serializer] into a [stream] using JSON format and UTF-8 encoding. * * @throws [SerializationException] if the given value cannot be serialized to JSON. - * @throws [IOException] If an I/O error occurs and stream can't be written to. + * @throws [IOException] If an I/O error occurs and stream cannot be written to. */ @ExperimentalSerializationApi public fun Json.encodeToStream( @@ -32,7 +32,7 @@ public fun Json.encodeToStream( * Serializes given [value] to [stream] using UTF-8 encoding and serializer retrieved from the reified type parameter. * * @throws [SerializationException] if the given value cannot be serialized to JSON. - * @throws [IOException] If an I/O error occurs and stream can't be written to. + * @throws [IOException] If an I/O error occurs and stream cannot be written to. */ @ExperimentalSerializationApi public inline fun Json.encodeToStream( @@ -48,7 +48,8 @@ public inline fun Json.encodeToStream( * and throws an exception if there are any dangling bytes after an object. * * @throws [SerializationException] if the given JSON input cannot be deserialized to the value of type [T]. - * @throws [IOException] If an I/O error occurs and stream can't be read from. + * @throws [IllegalArgumentException] if the decoded input cannot be represented as a valid instance of type [T] + * @throws [IOException] If an I/O error occurs and stream cannot be read from. */ @ExperimentalSerializationApi public fun Json.decodeFromStream( @@ -66,7 +67,8 @@ public fun Json.decodeFromStream( * and throws an exception if there are any dangling bytes after an object. * * @throws [SerializationException] if the given JSON input cannot be deserialized to the value of type [T]. - * @throws [IOException] If an I/O error occurs and stream can't be read from. + * @throws [IllegalArgumentException] if the decoded input cannot be represented as a valid instance of type [T] + * @throws [IOException] If an I/O error occurs and stream cannot be read from. */ @ExperimentalSerializationApi public inline fun Json.decodeFromStream(stream: InputStream): T = @@ -86,7 +88,8 @@ public inline fun Json.decodeFromStream(stream: InputStream): T = * closing it before returned sequence is evaluated completely will result in [IOException] from decoder. * * @throws [SerializationException] if the given JSON input cannot be deserialized to the value of type [T]. - * @throws [IOException] If an I/O error occurs and stream can't be read from. + * @throws [IllegalArgumentException] if the decoded input cannot be represented as a valid instance of type [T] + * @throws [IOException] If an I/O error occurs and stream cannot be read from. */ @ExperimentalSerializationApi public fun Json.decodeToSequence( @@ -110,7 +113,8 @@ public fun Json.decodeToSequence( * closing it before returned sequence is evaluated fully would result in [IOException] from decoder. * * @throws [SerializationException] if the given JSON input cannot be deserialized to the value of type [T]. - * @throws [IOException] If an I/O error occurs and stream can't be read from. + * @throws [IllegalArgumentException] if the decoded input cannot be represented as a valid instance of type [T] + * @throws [IOException] If an I/O error occurs and stream cannot be read from. */ @ExperimentalSerializationApi public inline fun Json.decodeToSequence(