summaryrefslogtreecommitdiff
path: root/formats/json-tests/jvmTest/src/kotlinx
diff options
context:
space:
mode:
Diffstat (limited to 'formats/json-tests/jvmTest/src/kotlinx')
-rw-r--r--formats/json-tests/jvmTest/src/kotlinx/serialization/BigDecimalTest.kt193
-rw-r--r--formats/json-tests/jvmTest/src/kotlinx/serialization/JavaCollectionsTest.kt112
-rw-r--r--formats/json-tests/jvmTest/src/kotlinx/serialization/JvmMissingFieldsExceptionTest.kt138
-rw-r--r--formats/json-tests/jvmTest/src/kotlinx/serialization/SerializationCasesTest.kt93
-rw-r--r--formats/json-tests/jvmTest/src/kotlinx/serialization/SerializeJavaClassTest.kt46
-rw-r--r--formats/json-tests/jvmTest/src/kotlinx/serialization/SerializerByTypeCacheTest.kt119
-rw-r--r--formats/json-tests/jvmTest/src/kotlinx/serialization/SerializerForNullableJavaTypeTest.kt44
-rw-r--r--formats/json-tests/jvmTest/src/kotlinx/serialization/StacktraceRecoveryTest.kt62
-rw-r--r--formats/json-tests/jvmTest/src/kotlinx/serialization/features/ContextualSerializationOnFileTest.kt41
-rw-r--r--formats/json-tests/jvmTest/src/kotlinx/serialization/features/InternalInheritanceTest.kt127
-rw-r--r--formats/json-tests/jvmTest/src/kotlinx/serialization/features/JsonJvmStreamsTest.kt131
-rw-r--r--formats/json-tests/jvmTest/src/kotlinx/serialization/features/JsonLazySequenceTest.kt197
-rw-r--r--formats/json-tests/jvmTest/src/kotlinx/serialization/features/JsonSequencePathTest.kt33
-rw-r--r--formats/json-tests/jvmTest/src/kotlinx/serialization/features/SerializerByTypeTest.kt292
-rw-r--r--formats/json-tests/jvmTest/src/kotlinx/serialization/json/GsonCompatibilityTest.kt56
-rw-r--r--formats/json-tests/jvmTest/src/kotlinx/serialization/json/JsonChunkedBase64DecoderTest.kt85
-rw-r--r--formats/json-tests/jvmTest/src/kotlinx/serialization/json/JsonConcurrentStressTest.kt78
-rw-r--r--formats/json-tests/jvmTest/src/kotlinx/serialization/json/MissingFieldExceptionWithPathTest.kt40
-rw-r--r--formats/json-tests/jvmTest/src/kotlinx/serialization/json/SpecConformanceTest.ktbin0 -> 5565 bytes
-rw-r--r--formats/json-tests/jvmTest/src/kotlinx/serialization/test/CurrentPlatform.kt8
-rw-r--r--formats/json-tests/jvmTest/src/kotlinx/serialization/test/JsonHelpers.kt20
-rw-r--r--formats/json-tests/jvmTest/src/kotlinx/serialization/test/TypeToken.kt17
22 files changed, 1932 insertions, 0 deletions
diff --git a/formats/json-tests/jvmTest/src/kotlinx/serialization/BigDecimalTest.kt b/formats/json-tests/jvmTest/src/kotlinx/serialization/BigDecimalTest.kt
new file mode 100644
index 00000000..a0c4c73a
--- /dev/null
+++ b/formats/json-tests/jvmTest/src/kotlinx/serialization/BigDecimalTest.kt
@@ -0,0 +1,193 @@
+package kotlinx.serialization
+
+import kotlinx.serialization.builtins.ListSerializer
+import kotlinx.serialization.builtins.MapSerializer
+import kotlinx.serialization.descriptors.PrimitiveKind
+import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
+import kotlinx.serialization.encoding.Decoder
+import kotlinx.serialization.encoding.Encoder
+import kotlinx.serialization.json.*
+import org.junit.Test
+import java.math.BigDecimal
+
+private typealias BigDecimalKxs = @Serializable(with = BigDecimalNumericSerializer::class) BigDecimal
+
+class BigDecimalTest : JsonTestBase() {
+
+ private val json = Json {
+ prettyPrint = true
+ }
+
+ private inline fun <reified T> assertBigDecimalJsonFormAndRestored(
+ expected: String,
+ actual: T,
+ serializer: KSerializer<T> = serializer(),
+ ) = assertJsonFormAndRestored(
+ serializer,
+ actual,
+ expected,
+ json
+ )
+
+ @Test
+ fun bigDecimal() {
+ fun test(expected: String, actual: BigDecimal) =
+ assertBigDecimalJsonFormAndRestored(expected, actual, BigDecimalNumericSerializer)
+
+ test("0", BigDecimal.ZERO)
+ test("1", BigDecimal.ONE)
+ test("-1", BigDecimal("-1"))
+ test("10", BigDecimal.TEN)
+ test(bdExpected1, bdActual1)
+ test(bdExpected2, bdActual2)
+ test(bdExpected3, bdActual3)
+ test(bdExpected4, bdActual4)
+ test(bdExpected5, bdActual5)
+ test(bdExpected6, bdActual6)
+ }
+
+ @Test
+ fun bigDecimalList() {
+
+ val bdList: List<BigDecimal> = listOf(
+ bdActual1,
+ bdActual2,
+ bdActual3,
+ bdActual4,
+ bdActual5,
+ bdActual6,
+ )
+
+ val expected =
+ """
+ [
+ $bdExpected1,
+ $bdExpected2,
+ $bdExpected3,
+ $bdExpected4,
+ $bdExpected5,
+ $bdExpected6
+ ]
+ """.trimIndent()
+
+ assertJsonFormAndRestored(
+ ListSerializer(BigDecimalNumericSerializer),
+ bdList,
+ expected,
+ json,
+ )
+ }
+
+ @Test
+ fun bigDecimalMap() {
+ val bdMap: Map<BigDecimal, BigDecimal> = mapOf(
+ bdActual1 to bdActual2,
+ bdActual3 to bdActual4,
+ bdActual5 to bdActual6,
+ )
+
+ val expected =
+ """
+ {
+ "$bdExpected1": $bdExpected2,
+ "$bdExpected3": $bdExpected4,
+ "$bdExpected5": $bdExpected6
+ }
+ """.trimIndent()
+
+ assertJsonFormAndRestored(
+ MapSerializer(BigDecimalNumericSerializer, BigDecimalNumericSerializer),
+ bdMap,
+ expected,
+ json,
+ )
+ }
+
+ @Test
+ fun bigDecimalHolder() {
+ val bdHolder = BigDecimalHolder(
+ bd = bdActual1,
+ bdList = listOf(
+ bdActual1,
+ bdActual2,
+ bdActual3,
+ ),
+ bdMap = mapOf(
+ bdActual1 to bdActual2,
+ bdActual3 to bdActual4,
+ bdActual5 to bdActual6,
+ ),
+ )
+
+ val expected =
+ """
+ {
+ "bd": $bdExpected1,
+ "bdList": [
+ $bdExpected1,
+ $bdExpected2,
+ $bdExpected3
+ ],
+ "bdMap": {
+ "$bdExpected1": $bdExpected2,
+ "$bdExpected3": $bdExpected4,
+ "$bdExpected5": $bdExpected6
+ }
+ }
+ """.trimIndent()
+
+ assertBigDecimalJsonFormAndRestored(
+ expected,
+ bdHolder,
+ )
+ }
+
+ companion object {
+
+ // test data
+ private val bdActual1 = BigDecimal("725345854747326287606413621318.311864440287151714280387858224")
+ private val bdActual2 = BigDecimal("336052472523017262165484244513.836582112201211216526831524328")
+ private val bdActual3 = BigDecimal("211054843014778386028147282517.011200287614476453868782405400")
+ private val bdActual4 = BigDecimal("364751025728628060231208776573.207325218263752602211531367642")
+ private val bdActual5 = BigDecimal("508257556021513833656664177125.824502734715222686411316853148")
+ private val bdActual6 = BigDecimal("127134584027580606401102614002.366672301517071543257300444000")
+
+ private const val bdExpected1 = "725345854747326287606413621318.311864440287151714280387858224"
+ private const val bdExpected2 = "336052472523017262165484244513.836582112201211216526831524328"
+ private const val bdExpected3 = "211054843014778386028147282517.011200287614476453868782405400"
+ private const val bdExpected4 = "364751025728628060231208776573.207325218263752602211531367642"
+ private const val bdExpected5 = "508257556021513833656664177125.824502734715222686411316853148"
+ private const val bdExpected6 = "127134584027580606401102614002.366672301517071543257300444000"
+ }
+
+}
+
+@Serializable
+private data class BigDecimalHolder(
+ val bd: BigDecimalKxs,
+ val bdList: List<BigDecimalKxs>,
+ val bdMap: Map<BigDecimalKxs, BigDecimalKxs>,
+)
+
+private object BigDecimalNumericSerializer : KSerializer<BigDecimal> {
+
+ override val descriptor = PrimitiveSerialDescriptor("java.math.BigDecimal", PrimitiveKind.DOUBLE)
+
+ override fun deserialize(decoder: Decoder): BigDecimal {
+ return if (decoder is JsonDecoder) {
+ BigDecimal(decoder.decodeJsonElement().jsonPrimitive.content)
+ } else {
+ BigDecimal(decoder.decodeString())
+ }
+ }
+
+ override fun serialize(encoder: Encoder, value: BigDecimal) {
+ val bdString = value.toPlainString()
+
+ if (encoder is JsonEncoder) {
+ encoder.encodeJsonElement(JsonUnquotedLiteral(bdString))
+ } else {
+ encoder.encodeString(bdString)
+ }
+ }
+}
diff --git a/formats/json-tests/jvmTest/src/kotlinx/serialization/JavaCollectionsTest.kt b/formats/json-tests/jvmTest/src/kotlinx/serialization/JavaCollectionsTest.kt
new file mode 100644
index 00000000..1f4958a6
--- /dev/null
+++ b/formats/json-tests/jvmTest/src/kotlinx/serialization/JavaCollectionsTest.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2019 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package kotlinx.serialization
+
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.test.typeTokenOf
+import org.junit.Test
+import java.util.HashMap
+import java.util.HashSet
+import kotlin.collections.LinkedHashMap
+import kotlin.collections.Map
+import kotlin.collections.hashMapOf
+import kotlin.collections.hashSetOf
+import kotlin.reflect.*
+import kotlin.test.*
+
+
+class JavaCollectionsTest {
+ @Serializable
+ data class HasHashMap(
+ val s: String,
+ val hashMap: HashMap<Int, String>,
+ val hashSet: HashSet<Int>,
+ val linkedHashMap: LinkedHashMap<Int, String>,
+ val kEntry: Map.Entry<Int, String>?
+ )
+
+ @Test
+ fun testJavaCollectionsInsideClass() {
+ val original = HasHashMap("42", hashMapOf(1 to "1", 2 to "2"), hashSetOf(11), LinkedHashMap(), null)
+ val serializer = HasHashMap.serializer()
+ val string = Json.encodeToString(serializer = serializer, value = original)
+ assertEquals(
+ expected = """{"s":"42","hashMap":{"1":"1","2":"2"},"hashSet":[11],"linkedHashMap":{},"kEntry":null}""",
+ actual = string
+ )
+ val restored = Json.decodeFromString(deserializer = serializer, string = string)
+ assertEquals(expected = original, actual = restored)
+ }
+
+ @Test
+ fun testTopLevelMaps() {
+ // Returning null here is a deliberate choice: map constructor functions may return different specialized
+ // implementations (e.g., kotlin.collections.EmptyMap or java.util.Collections.SingletonMap)
+ // that may or may not be generic. Since we generally cannot return a generic serializer using Java class only,
+ // all attempts to get map serializer using only .javaClass should return null.
+ assertNull(serializerOrNull(emptyMap<String, String>().javaClass))
+ assertNull(serializerOrNull(mapOf<String, String>("a" to "b").javaClass))
+ assertNull(serializerOrNull(mapOf<String, String>("a" to "b", "b" to "c").javaClass))
+ // Correct ways of retrieving map serializer:
+ assertContains(
+ serializer(typeTokenOf<Map<String, String>>()).descriptor.serialName,
+ "kotlin.collections.LinkedHashMap"
+ )
+ assertContains(
+ serializer(typeTokenOf<java.util.LinkedHashMap<String, String>>()).descriptor.serialName,
+ "kotlin.collections.LinkedHashMap"
+ )
+ assertContains(
+ serializer(typeOf<LinkedHashMap<String, String>>()).descriptor.serialName,
+ "kotlin.collections.LinkedHashMap"
+ )
+ }
+
+ @Test
+ fun testTopLevelSetsAndLists() {
+ // Same reasoning as for maps
+ assertNull(serializerOrNull(emptyList<String>().javaClass))
+ assertNull(serializerOrNull(listOf<String>("a").javaClass))
+ assertNull(serializerOrNull(listOf<String>("a", "b").javaClass))
+ assertNull(serializerOrNull(emptySet<String>().javaClass))
+ assertNull(serializerOrNull(setOf<String>("a").javaClass))
+ assertNull(serializerOrNull(setOf<String>("a", "b").javaClass))
+ assertContains(
+ serializer(typeTokenOf<Set<String>>()).descriptor.serialName,
+ "kotlin.collections.LinkedHashSet"
+ )
+ assertContains(
+ serializer(typeTokenOf<List<String>>()).descriptor.serialName,
+ "kotlin.collections.ArrayList"
+ )
+ assertContains(
+ serializer(typeTokenOf<java.util.LinkedHashSet<String>>()).descriptor.serialName,
+ "kotlin.collections.LinkedHashSet"
+ )
+ assertContains(
+ serializer(typeTokenOf<java.util.ArrayList<String>>()).descriptor.serialName,
+ "kotlin.collections.ArrayList"
+ )
+ }
+
+ @Test
+ fun testAnonymousObject() {
+ val obj: Any = object {}
+ assertNull(serializerOrNull(obj.javaClass))
+ }
+}
+
diff --git a/formats/json-tests/jvmTest/src/kotlinx/serialization/JvmMissingFieldsExceptionTest.kt b/formats/json-tests/jvmTest/src/kotlinx/serialization/JvmMissingFieldsExceptionTest.kt
new file mode 100644
index 00000000..a423bc84
--- /dev/null
+++ b/formats/json-tests/jvmTest/src/kotlinx/serialization/JvmMissingFieldsExceptionTest.kt
@@ -0,0 +1,138 @@
+package kotlinx.serialization
+
+import org.junit.Test
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.modules.SerializersModule
+import kotlinx.serialization.modules.polymorphic
+import kotlinx.serialization.modules.subclass
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertTrue
+
+class JvmMissingFieldsExceptionTest {
+ @Serializable
+ data class Generic<out T1, out T2, out T3>(val f1: T1, val f2: T2, val f3: T3)
+
+ @Serializable
+ sealed class Parent(val p1: Int, val p2: Int = 2, val p3: Int) {
+ @Serializable
+ @SerialName("child")
+ data class Child(val c1: Int = 1, val c2: Int, val c3: Int = 3) : Parent(c1 + 1, 2, 3)
+ }
+
+ @Serializable
+ open class ShortPlaneClass(val f1: Int, val f2: Int, val f3: Int = 3, val f4: Int)
+
+ @Serializable
+ class WithTransient(val f1: Int, @Transient val f2: Int = 2, val f3: Int, val f4: Int)
+
+ @Serializable
+ abstract class SimpleAbstract(val p1: Int, val p2: Int)
+
+ @Serializable
+ @SerialName("a")
+ data class ChildA(val c1: Int, val c2: Int = 2, val c3: Int) : SimpleAbstract(0, 10)
+
+ @Serializable
+ data class PolymorphicWrapper(@Polymorphic val nested: SimpleAbstract)
+
+ @Serializable
+ class BigPlaneClass(
+ val f0: Int,
+ val f5: Int = 5,
+ val f6: Int,
+ val f7: Int = 7,
+ val f8: Int,
+ val f9: Int,
+ val f10: Int,
+ val f11: Int,
+ val f12: Int,
+ val f13: Int,
+ val f14: Int,
+ val f15: Int,
+ val f16: Int,
+ val f17: Int,
+ val f18: Int,
+ val f19: Int,
+ val f20: Int,
+ val f21: Int,
+ val f22: Int,
+ val f23: Int,
+ val f24: Int,
+ val f25: Int,
+ val f26: Int,
+ val f27: Int,
+ val f28: Int,
+ val f29: Int,
+ val f30: Int,
+ val f31: Int,
+ val f32: Int,
+ val f33: Int,
+ val f34: Int,
+ val f35: Int,
+ ) : ShortPlaneClass(1, 2, 3, 4)
+
+ @Test
+ fun testShortPlaneClass() {
+ assertFailsWithMessages(listOf("f2", "f4")) {
+ Json.decodeFromString<ShortPlaneClass>("""{"f1":1}""")
+ }
+ }
+
+ @Test
+ fun testBigPlaneClass() {
+ val missedFields = MutableList(36) { "f$it" }
+ val definedInJsonFields = arrayOf("f1", "f15", "f34")
+ val optionalFields = arrayOf("f3", "f5", "f7")
+ missedFields.removeAll(definedInJsonFields)
+ missedFields.removeAll(optionalFields)
+ assertFailsWithMessages(missedFields) {
+ Json.decodeFromString<BigPlaneClass>("""{"f1":1, "f15": 15, "f34": 34}""")
+ }
+ }
+
+ @Test
+ fun testAnnotatedPolymorphic() {
+ val module = SerializersModule {
+ polymorphic(SimpleAbstract::class, null) {
+ subclass(ChildA::class)
+ }
+ }
+
+ assertFailsWithMessages(listOf("p2", "c3")) {
+ Json {
+ serializersModule = module
+ }.decodeFromString<PolymorphicWrapper>("""{"nested": {"type": "a", "p1": 1, "c1": 11}}""")
+ }
+ }
+
+
+ @Test
+ fun testSealed() {
+ assertFailsWithMessages(listOf("p3", "c2")) {
+ Json.decodeFromString<Parent>("""{"type": "child", "p1":1, "c1": 11}""")
+ }
+ }
+
+ @Test
+ fun testTransient() {
+ assertFailsWithMessages(listOf("f3", "f4")) {
+ Json.decodeFromString<WithTransient>("""{"f1":1}""")
+ }
+ }
+
+ @Test
+ fun testGeneric() {
+ assertFailsWithMessages(listOf("f2", "f3")) {
+ Json.decodeFromString<Generic<Int, Int, Int>>("""{"f1":1}""")
+ }
+ }
+
+
+ private inline fun assertFailsWithMessages(fields: List<String>, block: () -> Unit) {
+ val exception = assertFailsWith(MissingFieldException::class, null, block)
+ val missedMessages = fields.filter { !exception.message!!.contains(it) }
+ assertEquals(exception.missingFields.sorted(), fields.sorted())
+ assertTrue(missedMessages.isEmpty(), "Expected message '${exception.message}' to contain substrings $fields")
+ }
+}
diff --git a/formats/json-tests/jvmTest/src/kotlinx/serialization/SerializationCasesTest.kt b/formats/json-tests/jvmTest/src/kotlinx/serialization/SerializationCasesTest.kt
new file mode 100644
index 00000000..cbef36f3
--- /dev/null
+++ b/formats/json-tests/jvmTest/src/kotlinx/serialization/SerializationCasesTest.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2018 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package kotlinx.serialization
+
+import kotlinx.serialization.json.*
+import org.junit.*
+import org.junit.Assert.*
+
+class SerializationCasesTest : JsonTestBase() {
+
+ @Serializable
+ data class Data1(val a: Int, val b: Int)
+
+ @Serializer(forClass = Data1::class)
+ object ExtDataSerializer1
+
+ @Test
+ fun testConstructorValProperties() {
+ val data = Data1(1, 2)
+
+ // Serialize with internal serializer for Data class
+ assertEquals("""{"a":1,"b":2}""", default.encodeToString(data))
+ assertEquals(data, Json.decodeFromString<Data1>("""{"a":1,"b":2}"""))
+
+ // Serialize with external serializer for Data class
+ assertEquals("""{"a":1,"b":2}""", default.encodeToString(ExtDataSerializer1, data))
+ assertEquals(data, Json.decodeFromString(ExtDataSerializer1, """{"a":1,"b":2}"""))
+ }
+
+ @Serializable
+ class Data2 {
+ var a = 0
+ var b = 0
+ override fun equals(other: Any?) = other is Data2 && other.a == a && other.b == b
+ }
+
+ @Serializer(forClass=Data2::class)
+ object ExtDataSerializer2
+
+ @Test
+ fun testBodyVarProperties() {
+ val data = Data2().apply {
+ a = 1
+ b = 2
+ }
+
+ // Serialize with internal serializer for Data class
+ assertEquals("""{"a":1,"b":2}""", default.encodeToString(data))
+ assertEquals(data, Json.decodeFromString<Data2>("""{"a":1,"b":2}"""))
+
+ // Serialize with external serializer for Data class
+ assertEquals("""{"a":1,"b":2}""", default.encodeToString(ExtDataSerializer2, data))
+ assertEquals(data, Json.decodeFromString(ExtDataSerializer2, """{"a":1,"b":2}"""))
+ }
+
+ enum class TintEnum { LIGHT, DARK }
+
+ @Serializable
+ data class Data3(
+ val a: String,
+ val b: List<Int>,
+ val c: Map<String, TintEnum>
+ )
+
+ // Serialize with external serializer for Data class
+ @Serializer(forClass = Data3::class)
+ object ExtDataSerializer3
+
+ @Test
+ fun testNestedValues() {
+ val data = Data3("Str", listOf(1, 2), mapOf("lt" to TintEnum.LIGHT, "dk" to TintEnum.DARK))
+ // Serialize with internal serializer for Data class
+ val expected = """{"a":"Str","b":[1,2],"c":{"lt":"LIGHT","dk":"DARK"}}"""
+ assertEquals(expected, default.encodeToString(data))
+ assertEquals(data, Json.decodeFromString<Data3>(expected))
+ assertEquals(expected, default.encodeToString(ExtDataSerializer3, data))
+ assertEquals(data, Json.decodeFromString(ExtDataSerializer3, expected))
+ }
+}
diff --git a/formats/json-tests/jvmTest/src/kotlinx/serialization/SerializeJavaClassTest.kt b/formats/json-tests/jvmTest/src/kotlinx/serialization/SerializeJavaClassTest.kt
new file mode 100644
index 00000000..a2f900d0
--- /dev/null
+++ b/formats/json-tests/jvmTest/src/kotlinx/serialization/SerializeJavaClassTest.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization
+
+import kotlinx.serialization.descriptors.*
+import kotlinx.serialization.encoding.*
+import kotlinx.serialization.json.Json
+import org.junit.Test
+import java.text.DateFormat
+import java.text.SimpleDateFormat
+import java.util.*
+import kotlin.test.assertEquals
+
+object DateSerializer : KSerializer<Date> {
+ override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("java.util.Date", PrimitiveKind.STRING)
+
+ // Consider wrapping in ThreadLocal if serialization may happen in multiple threads
+ private val df: DateFormat = SimpleDateFormat("dd/MM/yyyy HH:mm:ss.SSS").apply {
+ timeZone = TimeZone.getTimeZone("GMT+2")
+ }
+
+ override fun serialize(encoder: Encoder, value: Date) {
+ encoder.encodeString(df.format(value))
+ }
+
+ override fun deserialize(decoder: Decoder): Date {
+ return df.parse(decoder.decodeString())
+ }
+}
+
+@Serializable
+data class ClassWithDate(@Serializable(with = DateSerializer::class) val date: Date)
+
+class SerializeJavaClassTest {
+ @Test
+ fun serializeToStringAndRestore() {
+ // Thursday, 4 October 2018 09:00:00 GMT+02:00 — KotlinConf 2018 Keynote
+ val date = ClassWithDate(Date(1538636400000L))
+ val s = Json.encodeToString(date)
+ assertEquals("""{"date":"04/10/2018 09:00:00.000"}""", s)
+ val date2 = Json.decodeFromString(ClassWithDate.serializer(), s)
+ assertEquals(date, date2)
+ }
+}
diff --git a/formats/json-tests/jvmTest/src/kotlinx/serialization/SerializerByTypeCacheTest.kt b/formats/json-tests/jvmTest/src/kotlinx/serialization/SerializerByTypeCacheTest.kt
new file mode 100644
index 00000000..b8dd35d1
--- /dev/null
+++ b/formats/json-tests/jvmTest/src/kotlinx/serialization/SerializerByTypeCacheTest.kt
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization
+
+import kotlinx.serialization.json.Json
+import java.net.URLClassLoader
+import kotlin.reflect.*
+import kotlin.test.*
+
+class SerializerByTypeCacheTest {
+
+ @Serializable
+ class Holder(val i: Int)
+
+ @Suppress("UNCHECKED_CAST")
+ @Test
+ fun testCaching() {
+ val typeOfKType = typeOf<Holder>()
+ val parameterKType = typeOf<List<Holder>>().arguments[0].type!!
+ assertSame(serializer(), serializer<Holder>())
+ assertSame(serializer(typeOfKType), serializer(typeOfKType))
+ assertSame(serializer(parameterKType), serializer(parameterKType))
+ assertSame(serializer(), serializer(typeOfKType) as KSerializer<Holder>)
+ assertSame(serializer(parameterKType) as KSerializer<Holder>, serializer(typeOfKType) as KSerializer<Holder>)
+ }
+
+ /**
+ * Checking the case when a parameterized type is loaded in different parallel [ClassLoader]s.
+ *
+ * If the main type is loaded by a common parent [ClassLoader] (for example, a bootstrap for [List]),
+ * and the element class is loaded by different loaders, then some implementations of the [KType] (e.g. `KTypeImpl` from reflection) may not see the difference between them.
+ *
+ * As a result, a serializer for another loader will be returned from the cache, and it will generate instances, when working with which we will get an [ClassCastException].
+ *
+ * The test checks the correctness of the cache for such cases - that different serializers for different loaders will be returned.
+ *
+ * [see](https://youtrack.jetbrains.com/issue/KT-54523).
+ */
+ @Test
+ fun testDifferentClassLoaders() {
+ val elementKType1 = SimpleKType(loadClass().kotlin)
+ val elementKType2 = SimpleKType(loadClass().kotlin)
+
+ // Java class must be same (same name)
+ assertEquals(elementKType1.classifier.java.canonicalName, elementKType2.classifier.java.canonicalName)
+ // class loaders must be different
+ assertNotSame(elementKType1.classifier.java.classLoader, elementKType2.classifier.java.classLoader)
+ // due to the incorrect definition of the `equals`, KType-s are equal
+ assertEquals(elementKType1, elementKType2)
+
+ // create parametrized type `List<Foo>`
+ val kType1 = SingleParametrizedKType(List::class, elementKType1)
+ val kType2 = SingleParametrizedKType(List::class, elementKType2)
+
+ val serializer1 = serializer(kType1)
+ val serializer2 = serializer(kType2)
+
+ // when taking a serializers from cache, we must distinguish between KType-s, despite the fact that they are equivalent
+ assertNotSame(serializer1, serializer2)
+
+ // serializers must work correctly
+ Json.decodeFromString(serializer1, "[{\"i\":1}]")
+ Json.decodeFromString(serializer2, "[{\"i\":1}]")
+ }
+
+ /**
+ * Load class `example.Foo` via new class loader. Compiled class-file located in the resources.
+ */
+ private fun loadClass(): Class<*> {
+ val classesUrl = this::class.java.classLoader.getResource("class_loaders/classes/")
+ val loader1 = URLClassLoader(arrayOf(classesUrl), this::class.java.classLoader)
+ return loader1.loadClass("example.Foo")
+ }
+
+ private class SimpleKType(override val classifier: KClass<*>): KType {
+ override val annotations: List<Annotation> = emptyList()
+ override val arguments: List<KTypeProjection> = emptyList()
+
+ override val isMarkedNullable: Boolean = false
+
+ override fun equals(other: Any?): Boolean {
+ if (other !is SimpleKType) return false
+ return classifier.java.canonicalName == other.classifier.java.canonicalName
+ }
+
+ override fun hashCode(): Int {
+ return classifier.java.canonicalName.hashCode()
+ }
+ }
+
+
+ private class SingleParametrizedKType(override val classifier: KClass<*>, val parameterType: KType): KType {
+ override val annotations: List<Annotation> = emptyList()
+
+ override val arguments: List<KTypeProjection> = listOf(KTypeProjection(KVariance.INVARIANT, parameterType))
+
+ override val isMarkedNullable: Boolean = false
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+
+ other as SingleParametrizedKType
+
+ if (classifier != other.classifier) return false
+ if (parameterType != other.parameterType) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = classifier.hashCode()
+ result = 31 * result + parameterType.hashCode()
+ return result
+ }
+ }
+}
diff --git a/formats/json-tests/jvmTest/src/kotlinx/serialization/SerializerForNullableJavaTypeTest.kt b/formats/json-tests/jvmTest/src/kotlinx/serialization/SerializerForNullableJavaTypeTest.kt
new file mode 100644
index 00000000..ebed6f37
--- /dev/null
+++ b/formats/json-tests/jvmTest/src/kotlinx/serialization/SerializerForNullableJavaTypeTest.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization
+
+import kotlinx.serialization.descriptors.*
+import kotlinx.serialization.encoding.*
+import kotlinx.serialization.json.*
+import org.junit.Test
+import java.util.*
+import kotlin.test.*
+
+class SerializerForNullableJavaTypeTest {
+
+ // User-reported generic serialization
+ class DateSerializer : KSerializer<Date?> {
+
+ override val descriptor = PrimitiveSerialDescriptor("LocalTime?", PrimitiveKind.LONG).nullable
+
+ override fun deserialize(decoder: Decoder): Date? = when (val seconds = decoder.decodeLong()) {
+ -1L -> null
+ else -> Date(seconds)
+ }
+
+ override fun serialize(encoder: Encoder, value: Date?) {
+ when (value) {
+ null -> encoder.encodeLong(-1L) //this line is never reached despite that nulls exist in serialized lists
+ else -> encoder.encodeLong(value.toInstant().toEpochMilli())
+ }
+ }
+ }
+
+ @Serializable
+ private data class ListWrapper(val times: List<@Serializable(with = DateSerializer::class) Date?>)
+
+ @Test
+ fun testMixedList() {
+ val data = ListWrapper(listOf(Date(42), null))
+ val str = Json.encodeToString(data)
+ assertEquals("""{"times":[42,-1]}""", str)
+ assertEquals(data, Json.decodeFromString(str))
+ }
+}
diff --git a/formats/json-tests/jvmTest/src/kotlinx/serialization/StacktraceRecoveryTest.kt b/formats/json-tests/jvmTest/src/kotlinx/serialization/StacktraceRecoveryTest.kt
new file mode 100644
index 00000000..dcf3e959
--- /dev/null
+++ b/formats/json-tests/jvmTest/src/kotlinx/serialization/StacktraceRecoveryTest.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization
+
+import kotlinx.coroutines.*
+import kotlinx.serialization.descriptors.*
+import kotlinx.serialization.encoding.*
+import kotlinx.serialization.json.*
+import kotlinx.serialization.json.internal.*
+import kotlinx.serialization.modules.*
+import kotlin.test.*
+
+class StacktraceRecoveryTest {
+ @Serializable
+ private class Data(val s: String)
+
+ private class BadDecoder : AbstractDecoder() {
+ override val serializersModule: SerializersModule = EmptySerializersModule()
+ override fun decodeElementIndex(descriptor: SerialDescriptor): Int = 42
+ }
+
+ @Test
+ fun testJsonDecodingException() = checkRecovered("JsonDecodingException") {
+ Json.decodeFromString<String>("42")
+ }
+
+ @Test
+ fun testJsonEncodingException() = checkRecovered("JsonEncodingException") {
+ Json.encodeToString(Double.NaN)
+ }
+
+ @Test
+ // checks simple name because UFE is internal class
+ fun testUnknownFieldException() = checkRecovered("UnknownFieldException") {
+ val serializer = Data.serializer()
+ serializer.deserialize(BadDecoder())
+ }
+
+ private fun checkRecovered(exceptionClassSimpleName: String, block: () -> Unit) = runBlocking {
+ val result = runCatching {
+ callBlockWithRecovery(block)
+ }
+ assertTrue(result.isFailure, "Block should have failed")
+ val e = result.exceptionOrNull()!!
+ assertEquals(exceptionClassSimpleName, e::class.simpleName!!)
+ val cause = e.cause
+ assertNotNull(cause, "Exception should have cause: $e")
+ assertEquals(e.message, cause.message)
+ assertEquals(exceptionClassSimpleName, e::class.simpleName!!)
+ }
+
+ // KLUDGE: A separate function with state-machine to ensure coroutine DebugMetadata is generated. See KT-41789
+ private suspend fun callBlockWithRecovery(block: () -> Unit) {
+ yield()
+ // use withContext to perform switch between coroutines and thus trigger exception recovery machinery
+ withContext(NonCancellable) {
+ block()
+ }
+ }
+}
diff --git a/formats/json-tests/jvmTest/src/kotlinx/serialization/features/ContextualSerializationOnFileTest.kt b/formats/json-tests/jvmTest/src/kotlinx/serialization/features/ContextualSerializationOnFileTest.kt
new file mode 100644
index 00000000..a190a483
--- /dev/null
+++ b/formats/json-tests/jvmTest/src/kotlinx/serialization/features/ContextualSerializationOnFileTest.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// TODO: Move to common tests after https://youtrack.jetbrains.com/issue/KT-28927 is fixed
+
+@file:UseContextualSerialization(Int::class, IntHolder::class)
+
+package kotlinx.serialization.features
+
+import kotlinx.serialization.*
+import kotlinx.serialization.json.*
+import kotlinx.serialization.modules.*
+import kotlin.test.*
+
+@Serializable
+data class Carrier3(
+ val a: IntHolder,
+ val i: Int,
+ val nullable: Int?,
+ val nullableIntHolder: IntHolder?,
+ val nullableIntList: List<Int?> = emptyList(),
+ val nullableIntHolderNullableList: List<IntHolder?>? = null
+)
+
+class ContextualSerializationOnFileTest {
+ val module = SerializersModule {
+ contextual(DividingIntSerializer)
+ contextual(MultiplyingIntHolderSerializer)
+ }
+ val json = Json { serializersModule = module; encodeDefaults = true }
+
+ @Test
+ fun testOnFile() {
+ val str = json.encodeToString(Carrier3.serializer(), Carrier3(IntHolder(42), 8, 8, IntHolder(42)))
+ assertEquals(
+ """{"a":84,"i":4,"nullable":4,"nullableIntHolder":84,"nullableIntList":[],"nullableIntHolderNullableList":null}""",
+ str
+ )
+ }
+}
diff --git a/formats/json-tests/jvmTest/src/kotlinx/serialization/features/InternalInheritanceTest.kt b/formats/json-tests/jvmTest/src/kotlinx/serialization/features/InternalInheritanceTest.kt
new file mode 100644
index 00000000..6a1f722e
--- /dev/null
+++ b/formats/json-tests/jvmTest/src/kotlinx/serialization/features/InternalInheritanceTest.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2017-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.features
+
+import kotlinx.serialization.*
+import kotlinx.serialization.json.*
+import org.junit.*
+import org.junit.Assert.*
+
+class InternalInheritanceTest : JsonTestBase() {
+ @Serializable
+ open class A(val parent: Int) {
+ private val rootOptional = "rootOptional"
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is A) return false
+
+ if (parent != other.parent) return false
+ if (rootOptional != other.rootOptional) return false
+
+ return true
+ }
+ }
+
+ @Serializable
+ open class B(val parent2: Int, @Transient val transientDerived: String = "X", val derived: String) : A(parent2) {
+ protected val bodyDerived = "body"
+ }
+
+ @Serializable
+ class C(val parent3: Int) : B(parent3, derived = "derived") {
+ val lastDerived = "optional"
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other?.javaClass != javaClass) return false
+
+ other as C
+
+ if (!super.equals(other)) return false
+ if (parent3 != other.parent3) return false
+ if (lastDerived != other.lastDerived) return false
+ if (parent2 != other.parent2) return false
+ if (transientDerived != other.transientDerived) return false
+ if (derived != other.derived) return false
+ if (bodyDerived != other.bodyDerived) return false
+
+ return true
+ }
+ }
+
+ @Test
+ fun testEncodeToString() {
+ assertEquals(
+ """{"parent":42,"rootOptional":"rootOptional","parent2":42,"derived":"derived",""" +
+ """"bodyDerived":"body","parent3":42,"lastDerived":"optional"}""",
+ default.encodeToString(C(42))
+ )
+ assertEquals(
+ """{"parent":13,"rootOptional":"rootOptional","parent2":13,"derived":"bbb","bodyDerived":"body"}""",
+ default.encodeToString(B(13, derived = "bbb"))
+ )
+ }
+
+ @Test
+ fun testParse() {
+ assertEquals(
+ C(42),
+ default.decodeFromString<C>(
+ """{"parent":42,"rootOptional":"rootOptional","parent2":42,""" +
+ """"derived":"derived","bodyDerived":"body","parent3":42,"lastDerived":"optional"}"""
+ )
+ )
+ assertEquals(
+ C(43),
+ default.decodeFromString<C>("""{"parent":43,"rootOptional":"rootOptional","parent2":43,"derived":"derived",""" +
+ """"bodyDerived":"body","parent3":43,"lastDerived":"optional"}""")
+ )
+ }
+
+ @Test
+ fun testParseOptionals() {
+ assertEquals(
+ B(100, derived = "wowstring"),
+ default.decodeFromString<B>("""{"parent":100,"rootOptional":"rootOptional","parent2":100,"derived":"wowstring","bodyDerived":"body"}""")
+ )
+ assertEquals(
+ C(44),
+ default.decodeFromString<C>("""{"parent":44, "parent2":44,"derived":"derived","bodyDerived":"body","parent3":44}""")
+ )
+ assertEquals(
+ B(101, derived = "wowstring"),
+ default.decodeFromString<B>("""{"parent":101,"parent2":101,"derived":"wowstring","bodyDerived":"body"}""")
+ )
+ assertEquals(
+ A(77),
+ default.decodeFromString<A>("""{"parent":77,"rootOptional":"rootOptional"}""")
+ )
+ assertEquals(
+ A(78),
+ default.decodeFromString<A>("""{"parent":78}""")
+ )
+ }
+
+ @Test(expected = SerializationException::class)
+ fun testThrowTransient() {
+ Json.decodeFromString<B>("""{"parent":100,"rootOptional":"rootOptional","transientDerived":"X",""" +
+ """"parent2":100,"derived":"wowstring","bodyDerived":"body"}""")
+ }
+
+ @Test(expected = SerializationException::class)
+ fun testThrowMissingField() {
+ default.decodeFromString<C>("""{"parent":42,"rootOptional":"rootOptional","derived":"derived",""" +
+ """"bodyDerived":"body","parent3":42,"lastDerived":"optional"}""")
+ }
+
+ @Test
+ fun testSerializeAsParent() {
+ val obj1: A = B(77, derived = "derived")
+ val obj2: A = C(77)
+ assertEquals("""{"parent":77,"rootOptional":"rootOptional"}""", default.encodeToString(obj1))
+ assertEquals("""{"parent":77,"rootOptional":"rootOptional"}""", default.encodeToString(obj2))
+ }
+}
diff --git a/formats/json-tests/jvmTest/src/kotlinx/serialization/features/JsonJvmStreamsTest.kt b/formats/json-tests/jvmTest/src/kotlinx/serialization/features/JsonJvmStreamsTest.kt
new file mode 100644
index 00000000..7019edae
--- /dev/null
+++ b/formats/json-tests/jvmTest/src/kotlinx/serialization/features/JsonJvmStreamsTest.kt
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
+
+package kotlinx.serialization.features
+
+import kotlinx.serialization.*
+import kotlinx.serialization.builtins.serializer
+import kotlinx.serialization.json.*
+import kotlinx.serialization.json.internal.BATCH_SIZE
+import kotlinx.serialization.modules.*
+import kotlinx.serialization.test.*
+import org.junit.Test
+import java.io.ByteArrayInputStream
+import java.io.ByteArrayOutputStream
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+
+class JsonJvmStreamsTest {
+ private val strLen = BATCH_SIZE * 2 + 42
+
+ @Test
+ fun testParsesStringsLongerThanBuffer() {
+ val str = "a".repeat(strLen)
+ val input = """{"data":"$str"}"""
+ assertEquals(input, Json.encodeViaStream(StringData.serializer(), StringData(str)))
+ assertEquals(str, Json.decodeViaStream(StringData.serializer(), input).data)
+ assertEquals(str, Json.decodeViaStream(String.serializer(), "\"$str\""))
+ }
+
+ @Test
+ fun testSkipsWhitespacesLongerThanBuffer() {
+ val str = "a".repeat(strLen)
+ val ws = " ".repeat(strLen)
+ val input = """{"data":$ws"$str"}"""
+ assertEquals("""{"data":"$str"}""", Json.encodeViaStream(StringData.serializer(), StringData(str)))
+ assertEquals(str, Json.decodeViaStream(StringData.serializer(), input).data)
+ }
+
+ @Test
+ fun testHandlesEscapesLongerThanBuffer() {
+ val str = "\\t".repeat(strLen)
+ val expected = "\t".repeat(strLen)
+ val input = """{"data":"$str"}"""
+ assertEquals(input, Json.encodeViaStream(StringData.serializer(), StringData(expected)))
+ assertEquals(expected, Json.decodeViaStream(StringData.serializer(), input).data)
+ }
+
+ @Test
+ fun testHandlesLongLenientStrings() {
+ val str = "a".repeat(strLen)
+ val input = """{"data":$str}"""
+ val json = Json { isLenient = true }
+ assertEquals(str, json.decodeViaStream(StringData.serializer(), input).data)
+ assertEquals(str, json.decodeViaStream(String.serializer(), str))
+ }
+
+ @Test
+ fun testThrowsCorrectExceptionOnEof() {
+ assertFailsWith<SerializationException> {
+ Json.decodeViaStream(StringData.serializer(), """{"data":""")
+ }
+ assertFailsWith<SerializationException> {
+ Json.decodeViaStream(StringData.serializer(), "")
+ }
+ assertFailsWith<SerializationException> {
+ Json.decodeViaStream(String.serializer(), "\"")
+ }
+ }
+
+ @Test
+ fun testRandomEscapeSequences() {
+ repeat(1000) {
+ val s = generateRandomUnicodeString(strLen)
+ try {
+ val serializer = String.serializer()
+ val b = ByteArrayOutputStream()
+ Json.encodeToStream(serializer, s, b)
+ val restored = Json.decodeFromStream(serializer, ByteArrayInputStream(b.toByteArray()))
+ assertEquals(s, restored)
+ } catch (e: Throwable) {
+ // Not assertion error to preserve cause
+ throw IllegalStateException("Unexpectedly failed test, cause string: $s", e)
+ }
+ }
+ }
+
+ interface Poly
+
+ @Serializable
+ @SerialName("Impl")
+ data class Impl(val str: String) : Poly
+
+ @Test
+ fun testPolymorphismWhenCrossingBatchSizeNonLeadingKey() {
+ val json = Json {
+ serializersModule = SerializersModule {
+ polymorphic(Poly::class) {
+ subclass(Impl::class, Impl.serializer())
+ }
+ }
+ }
+
+ val longString = "a".repeat(BATCH_SIZE - 5)
+ val string = """{"str":"$longString", "type":"Impl"}"""
+ val golden = Impl(longString)
+
+ val deserialized = json.decodeViaStream(serializer<Poly>(), string)
+ assertEquals(golden, deserialized as Impl)
+ }
+
+ @Test
+ fun testPolymorphismWhenCrossingBatchSize() {
+ val json = Json {
+ serializersModule = SerializersModule {
+ polymorphic(Poly::class) {
+ subclass(Impl::class, Impl.serializer())
+ }
+ }
+ }
+
+ val aLotOfWhiteSpaces = " ".repeat(BATCH_SIZE - 5)
+ val string = """{$aLotOfWhiteSpaces"type":"Impl", "str":"value"}"""
+ val golden = Impl("value")
+
+ val deserialized = json.decodeViaStream(serializer<Poly>(), string)
+ assertEquals(golden, deserialized as Impl)
+ }
+}
diff --git a/formats/json-tests/jvmTest/src/kotlinx/serialization/features/JsonLazySequenceTest.kt b/formats/json-tests/jvmTest/src/kotlinx/serialization/features/JsonLazySequenceTest.kt
new file mode 100644
index 00000000..23887660
--- /dev/null
+++ b/formats/json-tests/jvmTest/src/kotlinx/serialization/features/JsonLazySequenceTest.kt
@@ -0,0 +1,197 @@
+/*
+ * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.features
+
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.*
+import kotlinx.coroutines.runBlocking
+import kotlinx.serialization.*
+import kotlinx.serialization.builtins.serializer
+import kotlinx.serialization.features.sealed.SealedChild
+import kotlinx.serialization.features.sealed.SealedParent
+import kotlinx.serialization.json.*
+import kotlinx.serialization.test.assertFailsWithMessage
+import kotlinx.serialization.test.assertFailsWithSerial
+import org.junit.Test
+import java.io.*
+import kotlin.test.*
+
+class JsonLazySequenceTest {
+ val json = Json
+
+ private suspend inline fun <reified T> Flow<T>.writeToStream(os: OutputStream) {
+ collect {
+ json.encodeToStream(it, os)
+ }
+ }
+
+ private suspend inline fun <reified T> Json.readFromStream(iss: InputStream): Flow<T> = flow {
+ val serial = serializer<T>()
+ val iter = iterateOverStream(iss, serial)
+ while (iter.hasNext()) {
+ emit(iter.next())
+ }
+ }.flowOn(Dispatchers.IO)
+
+ private val inputStringWsSeparated = """{"data":"a"}{"data":"b"}{"data":"c"}"""
+ private val inputStringWrapped = """[{"data":"a"},{"data":"b"},{"data":"c"}]"""
+ private val inputList = listOf(StringData("a"), StringData("b"), StringData("c"))
+
+ @Test
+ fun testEncodeSeveralItems() {
+ val items = inputList
+ val os = ByteArrayOutputStream()
+ runBlocking {
+ val f = flow<StringData> { items.forEach { emit(it) } }
+ f.writeToStream(os)
+ }
+
+ assertEquals(inputStringWsSeparated, os.toString(Charsets.UTF_8.name()))
+ }
+
+ @Test
+ fun testDecodeSeveralItems() {
+ val ins = ByteArrayInputStream(inputStringWsSeparated.encodeToByteArray())
+ assertFailsWithMessage<SerializationException>("EOF") {
+ json.decodeFromStream<StringData>(ins)
+ }
+ }
+
+ private inline fun <reified T> Iterator<T>.assertNext(expected: T) {
+ assertTrue(hasNext())
+ assertEquals(expected, next())
+ }
+
+ private fun <T> Json.iterateOverStream(stream: InputStream, deserializer: DeserializationStrategy<T>): Iterator<T> =
+ decodeToSequence(stream, deserializer).iterator()
+
+ private fun withInputs(vararg inputs: String = arrayOf(inputStringWsSeparated, inputStringWrapped), block: (InputStream) -> Unit) {
+ for (input in inputs) {
+ val res = runCatching { block(input.asInputStream()) }
+ if (res.isFailure) throw AssertionError("Failed test with input $input", res.exceptionOrNull())
+ }
+ }
+
+ private fun String.asInputStream() = ByteArrayInputStream(this.encodeToByteArray())
+
+ @Test
+ fun testIterateSeveralItems() = withInputs { ins ->
+ val iter = json.iterateOverStream(ins, StringData.serializer())
+ iter.assertNext(StringData("a"))
+ iter.assertNext(StringData("b"))
+ iter.assertNext(StringData("c"))
+ assertFalse(iter.hasNext())
+ assertFalse(iter.hasNext()) // Subsequent calls to .hasNext() should not throw EOF or anything
+ assertFailsWithMessage<SerializationException>("EOF") {
+ iter.next()
+ }
+ }
+
+ @Test
+ fun testDecodeToSequence() = withInputs { ins ->
+ val sequence = json.decodeToSequence(ins, StringData.serializer())
+ assertEquals(inputList, sequence.toList(), "For input $inputStringWsSeparated")
+ assertFailsWith<IllegalStateException> { sequence.toList() } // assert constrained once
+ }
+
+ @Test
+ fun testDecodeAsFlow() = withInputs { ins ->
+ val list = runBlocking {
+ buildList { json.readFromStream<StringData>(ins).toCollection(this) }
+ }
+ assertEquals(inputList, list)
+ }
+
+ @Test
+ fun testItemsSeparatedByWs() {
+ val input = "{\"data\":\"a\"} {\"data\":\"b\"}\n\t{\"data\":\"c\"}"
+ val ins = ByteArrayInputStream(input.encodeToByteArray())
+ assertEquals(inputList, json.decodeToSequence(ins, StringData.serializer()).toList())
+ }
+
+ @Test
+ fun testJsonElement() {
+ val list = listOf<JsonElement>(
+ buildJsonObject { put("foo", "bar") },
+ buildJsonObject { put("foo", "baz") },
+ JsonPrimitive(10),
+ JsonPrimitive("abacaba"),
+ buildJsonObject { put("foo", "qux") }
+ )
+ val inputWs = """${list[0]} ${list[1]} ${list[2]} ${list[3]} ${list[4]}"""
+ val decodedWs = json.decodeToSequence<JsonElement>(inputWs.asInputStream()).toList()
+ assertEquals(list, decodedWs, "Failed whitespace-separated test")
+ val inputArray = """[${list[0]}, ${list[1]},${list[2]} , ${list[3]} ,${list[4]}]"""
+ val decodedArrayWrap = json.decodeToSequence<JsonElement>(inputArray.asInputStream()).toList()
+ assertEquals(list, decodedArrayWrap, "Failed array-wrapped test")
+ }
+
+
+ @Test
+ fun testSealedClasses() {
+ val input = """{"type":"first child","i":1,"j":10} {"type":"first child","i":1,"j":11}"""
+ val iter = json.iterateOverStream(input.asInputStream(), SealedParent.serializer())
+ iter.assertNext(SealedChild(10))
+ iter.assertNext(SealedChild(11))
+ }
+
+ @Test
+ fun testMalformedArray() {
+ val input1 = """[1, 2, 3"""
+ val input2 = """[1, 2, 3]qwert"""
+ val input3 = """[1,2 3]"""
+ withInputs(input1, input2, input3) {
+ assertFailsWithSerial("JsonDecodingException") {
+ json.decodeToSequence(it, Int.serializer()).toList()
+ }
+ }
+ }
+
+ @Test
+ fun testMultilineArrays() {
+ val input = "[1,2,3]\n[4,5,6]\n[7,8,9]"
+ assertFailsWithSerial("JsonDecodingException") {
+ json.decodeToSequence<List<Int>>(input.asInputStream(), DecodeSequenceMode.AUTO_DETECT).toList()
+ }
+ assertFailsWithSerial("JsonDecodingException") {
+ json.decodeToSequence<Int>(input.asInputStream(), DecodeSequenceMode.AUTO_DETECT).toList()
+ }
+ assertFailsWithSerial("JsonDecodingException") { // we do not merge lists
+ json.decodeToSequence<Int>(input.asInputStream(), DecodeSequenceMode.ARRAY_WRAPPED).toList()
+ }
+ val parsed = json.decodeToSequence<List<Int>>(input.asInputStream(), DecodeSequenceMode.WHITESPACE_SEPARATED).toList()
+ val expected = listOf(listOf(1,2,3), listOf(4,5,6), listOf(7,8,9))
+ assertEquals(expected, parsed)
+ }
+
+ @Test
+ fun testStrictArrayCheck() {
+ assertFailsWithSerial("JsonDecodingException") {
+ json.decodeToSequence<StringData>(inputStringWsSeparated.asInputStream(), DecodeSequenceMode.ARRAY_WRAPPED)
+ }
+ }
+
+ @Test
+ fun testPaddedWs() {
+ val paddedWs = " $inputStringWsSeparated "
+ assertEquals(inputList, json.decodeToSequence(paddedWs.asInputStream(), StringData.serializer()).toList())
+ assertEquals(inputList, json.decodeToSequence(paddedWs.asInputStream(), StringData.serializer(), DecodeSequenceMode.WHITESPACE_SEPARATED).toList())
+ }
+
+ @Test
+ fun testPaddedArray() {
+ val paddedWs = " $inputStringWrapped "
+ assertEquals(inputList, json.decodeToSequence(paddedWs.asInputStream(), StringData.serializer()).toList())
+ assertEquals(inputList, json.decodeToSequence(paddedWs.asInputStream(), StringData.serializer(), DecodeSequenceMode.ARRAY_WRAPPED).toList())
+ }
+
+ @Test
+ fun testToIteratorAndBack() = withInputs { ins ->
+ val iterator = Json.decodeToSequence(ins, StringData.serializer()).iterator()
+ val values = iterator.asSequence().take(3).toList()
+ assertEquals(inputList, values)
+ assertFalse(iterator.hasNext())
+ }
+}
diff --git a/formats/json-tests/jvmTest/src/kotlinx/serialization/features/JsonSequencePathTest.kt b/formats/json-tests/jvmTest/src/kotlinx/serialization/features/JsonSequencePathTest.kt
new file mode 100644
index 00000000..554deab1
--- /dev/null
+++ b/formats/json-tests/jvmTest/src/kotlinx/serialization/features/JsonSequencePathTest.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.features
+
+import kotlinx.serialization.*
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.json.*
+import kotlinx.serialization.test.assertFailsWithMessage
+import org.junit.Test
+import java.io.*
+
+class JsonSequencePathTest {
+
+ @Serializable
+ class NestedData(val s: String)
+
+ @Serializable
+ class Data(val data: NestedData)
+
+ @Test
+ fun testFailure() {
+ val source = """{"data":{"s":"value"}}{"data":{"s":42}}{notevenreached}""".toStream()
+ val iterator = Json.decodeToSequence<Data>(source).iterator()
+ iterator.next() // Ignore
+ assertFailsWithMessage<SerializationException>(
+ "Expected quotation mark '\"', but had '4' instead at path: \$.data.s"
+ ) { iterator.next() }
+ }
+
+ private fun String.toStream() = ByteArrayInputStream(encodeToByteArray())
+}
diff --git a/formats/json-tests/jvmTest/src/kotlinx/serialization/features/SerializerByTypeTest.kt b/formats/json-tests/jvmTest/src/kotlinx/serialization/features/SerializerByTypeTest.kt
new file mode 100644
index 00000000..a600b9d7
--- /dev/null
+++ b/formats/json-tests/jvmTest/src/kotlinx/serialization/features/SerializerByTypeTest.kt
@@ -0,0 +1,292 @@
+/*
+ * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.features
+
+import kotlinx.serialization.*
+import kotlinx.serialization.builtins.*
+import kotlinx.serialization.descriptors.*
+import kotlinx.serialization.encoding.*
+import kotlinx.serialization.json.*
+import kotlinx.serialization.modules.*
+import kotlinx.serialization.test.*
+import org.junit.Test
+import java.lang.reflect.*
+import kotlin.reflect.*
+import kotlin.test.*
+import kotlin.time.*
+
+class SerializerByTypeTest {
+
+ private val json = Json
+
+ @Serializable
+ data class Box<out T>(val a: T)
+
+ @Serializable
+ data class Data(val l: List<String>, val b: Box<Int>)
+
+ @Serializable(WithCustomDefault.Companion::class)
+ data class WithCustomDefault(val n: Int) {
+
+ companion object: KSerializer<WithCustomDefault> {
+ override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("WithCustomDefault", PrimitiveKind.INT)
+ override fun serialize(encoder: Encoder, value: WithCustomDefault) = encoder.encodeInt(value.n)
+ override fun deserialize(decoder: Decoder) = WithCustomDefault(decoder.decodeInt())
+ }
+ }
+
+ object IntBoxToken : ParameterizedType {
+ override fun getRawType() = Box::class.java
+ override fun getOwnerType() = null
+ override fun getActualTypeArguments(): Array<Type> = arrayOf(Int::class.java)
+ }
+
+ @Serializable
+ object SerializableObject
+
+ @Serializable
+ data class WithNamedCompanion(val a: Int) {
+ companion object Named
+ }
+
+ @Test
+ fun testGenericParameter() {
+ val b = Box(42)
+ assertSerializedWithType(IntBoxToken, """{"a":42}""", b)
+ }
+
+ @Test
+ fun testNestedGenericParameter() {
+ val b = Box(Box(239))
+ assertSerializedWithType(typeTokenOf<Box<Box<Int>>>(), """{"a":{"a":239}}""", b)
+ }
+
+ @Test
+ fun testArray() {
+ val myArr = arrayOf("a", "b", "c")
+ val token = myArr::class.java
+ assertSerializedWithType(token, """["a","b","c"]""", myArr)
+ }
+
+ @Test
+ fun testList() {
+ val myArr = listOf("a", "b", "c")
+ val token = object : ParameterizedType {
+ override fun getRawType(): Type = List::class.java
+ override fun getOwnerType(): Type? = null
+ override fun getActualTypeArguments(): Array<Type> = arrayOf(String::class.java)
+ }
+ assertSerializedWithType(token, """["a","b","c"]""", myArr)
+ }
+
+ @Test
+ fun testListAsCollection() {
+ val myArr: Collection<String> = listOf("a", "b", "c")
+ val token = object : ParameterizedType {
+ override fun getRawType(): Type = Collection::class.java
+ override fun getOwnerType(): Type? = null
+ override fun getActualTypeArguments(): Array<Type> = arrayOf(String::class.java)
+ }
+ assertSerializedWithType(token, """["a","b","c"]""", myArr)
+ }
+
+
+ @Test
+ fun testReifiedArrayResolving() {
+ val myArr = arrayOf("a", "b", "c")
+ val token = typeTokenOf<Array<String>>()
+ assertSerializedWithType(token, """["a","b","c"]""", myArr)
+ }
+
+ @Test
+ fun testPrimitiveArrayResolving() {
+ val myArr = intArrayOf(1, 2, 3)
+ val token = IntArray::class.java
+ val name = serializer(token).descriptor.serialName
+ assertTrue(name.contains("IntArray"))
+ assertSerializedWithType(token, """[1,2,3]""", myArr)
+ }
+
+ @Test
+ fun testReifiedListResolving() {
+ val myList = listOf("a", "b", "c")
+ val token = typeTokenOf<List<String>>()
+ assertSerializedWithType(token, """["a","b","c"]""", myList)
+ }
+
+ @Test
+ fun testReifiedSetResolving() {
+ val mySet = setOf("a", "b", "c", "c")
+ val token = typeTokenOf<Set<String>>()
+ assertSerializedWithType(token, """["a","b","c"]""", mySet)
+ }
+
+ @Test
+ fun testReifiedMapResolving() {
+ val myMap = mapOf("a" to Data(listOf("c"), Box(6)))
+ val token = typeTokenOf<Map<String, Data>>()
+ assertSerializedWithType(token, """{"a":{"l":["c"],"b":{"a":6}}}""",myMap)
+ }
+
+ @Test
+ fun testNestedListResolving() {
+ val myList = listOf(listOf(listOf(1, 2, 3)), listOf())
+ val token = typeTokenOf<List<List<List<Int>>>>()
+ assertSerializedWithType(token, "[[[1,2,3]],[]]", myList)
+ }
+
+ @Test
+ fun testNestedArrayResolving() {
+ val myList = arrayOf(arrayOf(arrayOf(1, 2, 3)), arrayOf())
+ val token = typeTokenOf<Array<Array<Array<Int>>>>()
+ assertSerializedWithType(token, "[[[1,2,3]],[]]", myList)
+ }
+
+ @Test
+ fun testNestedMixedResolving() {
+ val myList = arrayOf(listOf(arrayOf(1, 2, 3)), listOf())
+ val token = typeTokenOf<Array<List<Array<Int>>>>()
+ assertSerializedWithType(token, "[[[1,2,3]],[]]", myList)
+ }
+
+ @Test
+ fun testPair() {
+ val myPair = "42" to 42
+ val token = typeTokenOf<Pair<String, Int>>()
+ assertSerializedWithType(token, """{"first":"42","second":42}""", myPair)
+ }
+
+ @Test
+ fun testTriple() {
+ val myTriple = Triple("1", 2, Box(42))
+ val token = typeTokenOf<Triple<String, Int, Box<Int>>>()
+ assertSerializedWithType(token, """{"first":"1","second":2,"third":{"a":42}}""", myTriple)
+ }
+
+ @Test
+ fun testGenericInHolder() {
+ val b = Data(listOf("a", "b", "c"), Box(42))
+ assertSerializedWithType(Data::class.java,"""{"l":["a","b","c"],"b":{"a":42}}""", b )
+ }
+
+ @Test
+ fun testOverriddenSerializer() {
+ val foo = json.decodeFromString<WithCustomDefault>("9")
+ assertEquals(9, foo.n)
+ }
+
+ @Test
+ fun testNamedCompanion() {
+ val namedCompanion = WithNamedCompanion(1)
+ assertSerializedWithType(WithNamedCompanion::class.java, """{"a":1}""", namedCompanion)
+ }
+
+ @Test
+ fun testPrimitive() {
+ val token = typeTokenOf<Int>()
+ val serial = serializer(token)
+ assertSame(Int.serializer() as KSerializer<*>, serial)
+ }
+
+ @Test
+ fun testObject() {
+ val token = typeTokenOf<SerializableObject>()
+ val serial = serializer(token)
+ assertEquals(SerializableObject.serializer().descriptor, serial.descriptor)
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ private fun <T> assertSerializedWithType(
+ token: Type,
+ expected: String,
+ value: T,
+ ) {
+ val serial = serializer(token) as KSerializer<T>
+ assertEquals(expected, json.encodeToString(serial, value))
+ val serial2 = requireNotNull(serializerOrNull(token)) { "Expected serializer to be found" }
+ assertEquals(expected, json.encodeToString(serial2 as KSerializer<T>, value))
+ }
+
+ class IntBox(val i: Int)
+
+ object CustomIntSerializer : KSerializer<IntBox> {
+ override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("CIS", PrimitiveKind.INT)
+
+ override fun serialize(encoder: Encoder, value: IntBox) {
+ encoder.encodeInt(42)
+ }
+
+ override fun deserialize(decoder: Decoder): IntBox {
+ TODO()
+ }
+ }
+
+ @Test
+ fun testContextualLookup() {
+ val module = SerializersModule { contextual(CustomIntSerializer) }
+ val serializer = module.serializer(typeTokenOf<List<List<IntBox>>>())
+ assertEquals("[[42]]", Json.encodeToString(serializer, listOf(listOf(IntBox(1)))))
+ }
+
+ @Test
+ fun testCompiledWinsOverContextual() {
+ val contextual = object : KSerializer<Int> {
+ override val descriptor: SerialDescriptor = Int.serializer().descriptor
+
+ override fun serialize(encoder: Encoder, value: Int) {
+ fail()
+ }
+
+ override fun deserialize(decoder: Decoder): Int {
+ fail()
+ }
+ }
+ val module = SerializersModule { contextual(contextual) }
+ val serializer = module.serializer(typeTokenOf<List<List<Int>>>())
+ assertEquals("[[1]]", Json.encodeToString(serializer, listOf(listOf<Int>(1))))
+ assertEquals("42", Json.encodeToString(module.serializer(typeTokenOf<Int>()), 42))
+ }
+
+ class NonSerializable
+
+ class NonSerializableBox<T>(val boxed: T)
+
+ @Test
+ fun testLookupFail() {
+ assertNull(serializerOrNull(typeTokenOf<NonSerializable>()))
+ assertNull(serializerOrNull(typeTokenOf<NonSerializableBox<String>>()))
+ assertNull(serializerOrNull(typeTokenOf<Box<NonSerializable>>()))
+
+ assertFailsWithMessage<SerializationException>("for class 'NonSerializable'") {
+ serializer(typeTokenOf<NonSerializable>())
+ }
+
+ assertFailsWithMessage<SerializationException>("for class 'NonSerializableBox'") {
+ serializer(typeTokenOf<NonSerializableBox<String>>())
+ }
+
+ assertFailsWithMessage<SerializationException>("for class 'NonSerializable'") {
+ serializer(typeTokenOf<kotlinx.serialization.Box<NonSerializable>>())
+ }
+
+ assertFailsWithMessage<SerializationException>("for class 'NonSerializable'") {
+ serializer(typeTokenOf<Array<NonSerializable>>())
+ }
+ }
+
+ @OptIn(ExperimentalTime::class)
+ @Test
+ fun testSerializersAreIntrinsified() {
+ val direct = measureTime {
+ Json.encodeToString(IntData.serializer(), IntData(10))
+ }
+ val directMs = direct.inWholeMicroseconds
+ val indirect = measureTime {
+ Json.encodeToString(IntData(10))
+ }
+ val indirectMs = indirect.inWholeMicroseconds
+ if (indirectMs > directMs + (directMs / 4)) error("Direct ($directMs) and indirect ($indirectMs) times are too far apart")
+ }
+}
diff --git a/formats/json-tests/jvmTest/src/kotlinx/serialization/json/GsonCompatibilityTest.kt b/formats/json-tests/jvmTest/src/kotlinx/serialization/json/GsonCompatibilityTest.kt
new file mode 100644
index 00000000..99bc23f9
--- /dev/null
+++ b/formats/json-tests/jvmTest/src/kotlinx/serialization/json/GsonCompatibilityTest.kt
@@ -0,0 +1,56 @@
+package kotlinx.serialization.json
+
+import com.google.gson.*
+import kotlinx.serialization.*
+import org.junit.Test
+import kotlin.test.*
+
+class GsonCompatibilityTest {
+
+ @Serializable
+ data class Box(val d: Double, val f: Float)
+
+ @Test
+ fun testNaN() {
+ checkCompatibility(Box(Double.NaN, 1.0f))
+ checkCompatibility(Box(1.0, Float.NaN))
+ checkCompatibility(Box(Double.NaN, Float.NaN))
+ }
+
+ @Test
+ fun testInfinity() {
+ checkCompatibility(Box(Double.POSITIVE_INFINITY, 1.0f))
+ checkCompatibility(Box(1.0, Float.POSITIVE_INFINITY))
+ checkCompatibility(Box(Double.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY))
+ }
+
+ @Test
+ fun testNumber() {
+ checkCompatibility(Box(23.9, 23.9f))
+ }
+
+ private fun checkCompatibility(box: Box) {
+ checkCompatibility(box, Gson(), Json)
+ checkCompatibility(box, GsonBuilder().serializeSpecialFloatingPointValues().create(), Json { allowSpecialFloatingPointValues = true })
+ }
+
+ private fun checkCompatibility(box: Box, gson: Gson, json: Json) {
+ val jsonResult = resultOrNull { json.encodeToString(box) }
+ val gsonResult = resultOrNull { gson.toJson(box) }
+ assertEquals(gsonResult, jsonResult)
+
+ if (jsonResult != null && gsonResult != null) {
+ val jsonDeserialized: Box = json.decodeFromString(jsonResult)
+ val gsonDeserialized: Box = gson.fromJson(gsonResult, Box::class.java)
+ assertEquals(gsonDeserialized, jsonDeserialized)
+ }
+ }
+
+ private fun resultOrNull(function: () -> String): String? {
+ return try {
+ function()
+ } catch (t: Throwable) {
+ null
+ }
+ }
+}
diff --git a/formats/json-tests/jvmTest/src/kotlinx/serialization/json/JsonChunkedBase64DecoderTest.kt b/formats/json-tests/jvmTest/src/kotlinx/serialization/json/JsonChunkedBase64DecoderTest.kt
new file mode 100644
index 00000000..35dc16fc
--- /dev/null
+++ b/formats/json-tests/jvmTest/src/kotlinx/serialization/json/JsonChunkedBase64DecoderTest.kt
@@ -0,0 +1,85 @@
+package kotlinx.serialization.json
+
+import kotlinx.serialization.*
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.descriptors.*
+import kotlinx.serialization.encoding.*
+import kotlinx.serialization.test.assertFailsWithMessage
+import org.junit.Test
+import java.io.*
+import java.util.*
+import kotlin.random.Random
+import kotlin.test.*
+
+
+@Serializable(with = LargeBase64StringSerializer::class)
+data class LargeBinaryData(val binaryData: ByteArray) {
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+
+ other as LargeBinaryData
+
+ if (!binaryData.contentEquals(other.binaryData)) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ return binaryData.contentHashCode()
+ }
+}
+
+@Serializable
+data class ClassWithBinaryDataField(val binaryField: LargeBinaryData)
+
+object LargeBase64StringSerializer : KSerializer<LargeBinaryData> {
+ private val b64Decoder: Base64.Decoder = Base64.getDecoder()
+ override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("LargeStringContent", PrimitiveKind.STRING)
+
+ override fun deserialize(decoder: Decoder): LargeBinaryData {
+ require(decoder is ChunkedDecoder) { "Only chunked decoder supported" }
+
+ var reminder = ""
+ val decodedBytes = ByteArrayOutputStream().use { bos ->
+ decoder.decodeStringChunked {
+ val actualChunk = reminder + it
+ val reminderLength = actualChunk.length % 4
+ val alignedLength = actualChunk.length - reminderLength
+ val alignedChunk = actualChunk.take(alignedLength)
+ reminder = actualChunk.takeLast(reminderLength)
+ bos.write(b64Decoder.decode(alignedChunk))
+ }
+ bos.toByteArray()
+ }
+
+ return LargeBinaryData(decodedBytes)
+ }
+
+ override fun serialize(encoder: Encoder, value: LargeBinaryData) {
+ encoder.encodeString(Base64.getEncoder().encodeToString(value.binaryData))
+ }
+}
+
+class JsonChunkedBase64DecoderTest : JsonTestBase() {
+
+ @Test
+ fun decodeBase64String() {
+ val sourceObject =
+ ClassWithBinaryDataField(LargeBinaryData(Random.nextBytes(16 * 1024))) // After encoding to Base64 will be larger than 16k (JsonLexer#BATCH_SIZE)
+ val serializedObject = Json.encodeToString(sourceObject)
+
+ JsonTestingMode.values().forEach { mode ->
+ if (mode == JsonTestingMode.TREE) {
+ assertFailsWithMessage<IllegalArgumentException>(
+ "Only chunked decoder supported", "Shouldn't decode JSON in TREE mode"
+ ) {
+ Json.decodeFromString<ClassWithBinaryDataField>(serializedObject, mode)
+ }
+ } else {
+ val deserializedObject = Json.decodeFromString<ClassWithBinaryDataField>(serializedObject, mode)
+ assertEquals(sourceObject.binaryField, deserializedObject.binaryField)
+ }
+ }
+ }
+}
diff --git a/formats/json-tests/jvmTest/src/kotlinx/serialization/json/JsonConcurrentStressTest.kt b/formats/json-tests/jvmTest/src/kotlinx/serialization/json/JsonConcurrentStressTest.kt
new file mode 100644
index 00000000..cdcbab79
--- /dev/null
+++ b/formats/json-tests/jvmTest/src/kotlinx/serialization/json/JsonConcurrentStressTest.kt
@@ -0,0 +1,78 @@
+package kotlinx.serialization.json
+
+import kotlinx.coroutines.*
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.builtins.*
+import org.junit.Test
+import java.io.ByteArrayInputStream
+import java.io.ByteArrayOutputStream
+import kotlin.random.*
+import kotlin.test.*
+
+// Stresses out that JSON decoded in parallel does not interfere (mostly via caching of various buffers)
+class JsonConcurrentStressTest : JsonTestBase() {
+ private val charset = "ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz0123456789"
+
+ @Test
+ fun testDecodeInParallelSimpleList() = doTest(100) { mode ->
+ val value = (1..10000).map { Random.nextDouble() }
+ val string = Json.encodeToString(ListSerializer(Double.serializer()), value, mode)
+ assertEquals(value, Json.decodeFromString(ListSerializer(Double.serializer()), string, mode))
+ }
+
+ @Serializable
+ data class Foo(val s: String, val f: Foo?)
+
+ @Test
+ fun testDecodeInParallelListOfPojo() = doTest(1_000) { mode ->
+ val value = (1..100).map {
+ val randomString = getRandomString()
+ val nestedFoo = Foo("null抢\u000E鋽윝䑜厼\uF70A紲ᢨ䣠null⛾䉻嘖緝ᯧnull쎶\u0005null" + randomString, null)
+ Foo(getRandomString(), nestedFoo)
+ }
+ val string = Json.encodeToString(ListSerializer(Foo.serializer()), value, mode)
+ assertEquals(value, Json.decodeFromString(ListSerializer(Foo.serializer()), string, mode))
+ }
+
+ @Test
+ fun testDecodeInParallelPojo() = doTest(100_000) { mode ->
+ val randomString = getRandomString()
+ val nestedFoo = Foo("null抢\u000E鋽윝䑜厼\uF70A紲ᢨ䣠null⛾䉻嘖緝ᯧnull쎶\u0005null" + randomString, null)
+ val randomFoo = Foo(getRandomString(), nestedFoo)
+ val string = Json.encodeToString(Foo.serializer(), randomFoo, mode)
+ assertEquals(randomFoo, Json.decodeFromString(Foo.serializer(), string, mode))
+ }
+
+ @Test
+ fun testDecodeInParallelSequencePojo() = runBlocking<Unit> {
+ for (i in 1 until 1_000) {
+ launch(Dispatchers.Default) {
+ val values = (1..100).map {
+ val randomString = getRandomString()
+ val nestedFoo = Foo("null抢\u000E鋽윝䑜厼\uF70A紲ᢨ䣠null⛾䉻嘖緝ᯧnull쎶\u0005null" + randomString, null)
+ Foo(getRandomString(), nestedFoo)
+ }
+ val baos = ByteArrayOutputStream()
+ for (value in values) {
+ Json.encodeToStream(Foo.serializer(), value, baos)
+ }
+ val bais = ByteArrayInputStream(baos.toByteArray())
+ assertEquals(values, Json.decodeToSequence(bais, Foo.serializer()).toList())
+ }
+ }
+ }
+
+ private fun getRandomString() = (1..Random.nextInt(0, charset.length)).map { charset[it] }.joinToString(separator = "")
+
+ private fun doTest(iterations: Int, block: (JsonTestingMode) -> Unit) {
+ runBlocking<Unit> {
+ for (i in 1 until iterations) {
+ launch(Dispatchers.Default) {
+ parametrizedTest {
+ block(it)
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/formats/json-tests/jvmTest/src/kotlinx/serialization/json/MissingFieldExceptionWithPathTest.kt b/formats/json-tests/jvmTest/src/kotlinx/serialization/json/MissingFieldExceptionWithPathTest.kt
new file mode 100644
index 00000000..e56f173e
--- /dev/null
+++ b/formats/json-tests/jvmTest/src/kotlinx/serialization/json/MissingFieldExceptionWithPathTest.kt
@@ -0,0 +1,40 @@
+package kotlinx.serialization.json
+
+import kotlinx.serialization.*
+import kotlinx.serialization.json.Json.Default.decodeFromString
+import org.junit.*
+import org.junit.Test
+import java.io.ByteArrayInputStream
+import java.io.ByteArrayOutputStream
+import java.io.PrintWriter
+import java.io.StringWriter
+import kotlin.test.*
+
+class MissingFieldExceptionWithPathTest {
+
+ @Test // Repro for #2212
+ fun testMfeIsNotReappliedMultipleTimes() {
+ val inputMalformed = """{"title": "...","cast": [{}]"""
+ try {
+ Json.decodeFromString<Movie>(inputMalformed)
+ fail("Unreacheable state")
+ } catch (e: MissingFieldException) {
+ val fullStackTrace = e.stackTraceToString()
+ val i1 = fullStackTrace.toString().indexOf("at path")
+ val i2 = fullStackTrace.toString().lastIndexOf("at path")
+ assertEquals(i1, i2)
+ assertTrue(i1 != -1)
+ }
+ }
+
+ @Serializable
+ data class Movie(
+ val title: String,
+ val cast: List<Cast>,
+ )
+
+ @Serializable
+ data class Cast(
+ val name: String
+ )
+}
diff --git a/formats/json-tests/jvmTest/src/kotlinx/serialization/json/SpecConformanceTest.kt b/formats/json-tests/jvmTest/src/kotlinx/serialization/json/SpecConformanceTest.kt
new file mode 100644
index 00000000..96401f72
--- /dev/null
+++ b/formats/json-tests/jvmTest/src/kotlinx/serialization/json/SpecConformanceTest.kt
Binary files differ
diff --git a/formats/json-tests/jvmTest/src/kotlinx/serialization/test/CurrentPlatform.kt b/formats/json-tests/jvmTest/src/kotlinx/serialization/test/CurrentPlatform.kt
new file mode 100644
index 00000000..91aa17f0
--- /dev/null
+++ b/formats/json-tests/jvmTest/src/kotlinx/serialization/test/CurrentPlatform.kt
@@ -0,0 +1,8 @@
+/*
+ * Copyright 2017-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.test
+
+
+public actual val currentPlatform: Platform = Platform.JVM
diff --git a/formats/json-tests/jvmTest/src/kotlinx/serialization/test/JsonHelpers.kt b/formats/json-tests/jvmTest/src/kotlinx/serialization/test/JsonHelpers.kt
new file mode 100644
index 00000000..9220bbd3
--- /dev/null
+++ b/formats/json-tests/jvmTest/src/kotlinx/serialization/test/JsonHelpers.kt
@@ -0,0 +1,20 @@
+package kotlinx.serialization.test
+
+import kotlinx.serialization.DeserializationStrategy
+import kotlinx.serialization.SerializationStrategy
+import kotlinx.serialization.json.*
+import java.io.ByteArrayOutputStream
+
+actual fun <T> Json.encodeViaStream(
+ serializer: SerializationStrategy<T>,
+ value: T
+): String {
+ val output = ByteArrayOutputStream()
+ encodeToStream(serializer, value, output)
+ return output.toString(Charsets.UTF_8.name())
+}
+
+actual fun <T> Json.decodeViaStream(
+ serializer: DeserializationStrategy<T>,
+ input: String
+): T = decodeFromStream(serializer, input.byteInputStream())
diff --git a/formats/json-tests/jvmTest/src/kotlinx/serialization/test/TypeToken.kt b/formats/json-tests/jvmTest/src/kotlinx/serialization/test/TypeToken.kt
new file mode 100644
index 00000000..2b04274c
--- /dev/null
+++ b/formats/json-tests/jvmTest/src/kotlinx/serialization/test/TypeToken.kt
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2017-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.test
+
+import java.lang.reflect.*
+
+
+@PublishedApi
+internal open class TypeBase<T>
+
+public inline fun <reified T> typeTokenOf(): Type {
+ val base = object : TypeBase<T>() {}
+ val superType = base::class.java.genericSuperclass!!
+ return (superType as ParameterizedType).actualTypeArguments.first()!!
+}