| /* |
| * Copyright 2017-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. |
| */ |
| @file:Suppress("UNCHECKED_CAST") |
| |
| package sample |
| |
| import kotlinx.serialization.* |
| import kotlinx.serialization.builtins.* |
| import kotlinx.serialization.json.* |
| import kotlinx.serialization.modules.* |
| import kotlin.reflect.* |
| import kotlin.test.* |
| |
| class JsonTest { |
| |
| private val originalData = Data("Hello") |
| private val originalString = |
| """{"s":"Hello","box":{"boxed":42},"boxes":{"desc":"boxes","boxes":[{"boxed":"foo"},{"boxed":"bar"}]},"m":{}}""" |
| private val nonstrict: Json = Json { |
| isLenient = true |
| ignoreUnknownKeys = true |
| allowSpecialFloatingPointValues = true |
| useArrayPolymorphism = true |
| |
| } |
| |
| @Test |
| fun testStringForm() { |
| val str = Json.encodeToString(Data.serializer(), originalData) |
| assertEquals(originalString, str) |
| } |
| |
| @Test |
| fun testSerializeBack() { |
| val restored = Json.decodeFromString(Data.serializer(), originalString) |
| assertEquals(originalData, restored) |
| } |
| |
| private fun genTestData(): Holder { |
| var cnt = -1 |
| fun gen(): MessageWithId { |
| cnt++ |
| return MessageWithId(cnt, "Message #$cnt") |
| } |
| |
| return Holder(gen(), listOf(gen(), gen()), gen(), setOf(SimpleMessage()), DoubleSimpleMessage("DoubleSimple"), gen()) |
| } |
| |
| private val testModule = SerializersModule { |
| listOf(Message::class, IMessage::class, SimpleMessage::class).forEach { clz -> |
| polymorphic(clz as KClass<Any>) { |
| subclass(SimpleMessage::class) |
| subclass(DoubleSimpleMessage::class) |
| subclass(MessageWithId::class) |
| } |
| } |
| } |
| |
| @Test |
| fun testEnablesImplicitlyOnInterfacesAndAbstractClasses() { |
| val json = Json { useArrayPolymorphism = true; prettyPrint = false; serializersModule = testModule } |
| val data = genTestData() |
| assertEquals("""{"iMessage":["MessageWithId",{"id":0,"body":"Message #0"}],"iMessageList":[["MessageWithId",{"id":1,"body":"Message #1"}],""" + |
| """["MessageWithId",{"id":2,"body":"Message #2"}]],"message":["MessageWithId",{"id":3,"body":"Message #3"}],"msgSet":[["SimpleMessage",""" + |
| """{"body":"Simple"}]],"simple":["DoubleSimpleMessage",{"body":"Simple","body2":"DoubleSimple"}],"withId":{"id":4,"body":"Message #4"}}""", |
| json.encodeToString(Holder.serializer(), data) |
| ) |
| } |
| |
| @Test |
| fun testPolymorphicForGenericUpperBound() { |
| val generic = GenericMessage<Message, Any>(MessageWithId(42, "body"), "body2") |
| val serial = GenericMessage.serializer(Message.serializer(), Int.serializer() as KSerializer<Any>) |
| val json = Json { useArrayPolymorphism = true; prettyPrint = false; serializersModule = testModule } |
| val s = json.encodeToString(serial, generic) |
| assertEquals("""{"value":["MessageWithId",{"id":42,"body":"body"}],"value2":["kotlin.String","body2"]}""", s) |
| } |
| |
| @Test |
| fun testDescriptor() { |
| val desc = Holder.serializer().descriptor |
| assertEquals(PolymorphicSerializer(IMessage::class).descriptor, desc.getElementDescriptor(0)) |
| } |
| |
| @Test |
| fun canBeSerializedAsDerived() { |
| val derived = Derived(42) |
| val msg = Json.encodeToString(Derived.serializer(), derived) |
| assertEquals("""{"publicState":"A","privateState":"B","derivedState":42,"rootState":"foo"}""", msg) |
| val d2 = Json.decodeFromString(Derived.serializer(), msg) |
| assertEquals(derived, d2) |
| } |
| |
| @Test |
| fun canBeSerializedAsParent() { |
| val derived = Derived(42) |
| val msg = Json.encodeToString(SerializableBase.serializer(), derived) |
| assertEquals("""{"publicState":"A","privateState":"B"}""", msg) |
| val d2 = Json.decodeFromString(SerializableBase.serializer(), msg) |
| assertEquals(SerializableBase(), d2) |
| // no derivedState |
| assertFailsWith<SerializationException> { Json.decodeFromString(Derived.serializer(), msg) } |
| } |
| |
| @Test |
| fun testWithOpenProperty() { |
| val d = Derived2("foo") |
| val msgFull = Json.encodeToString(Derived2.serializer(), d) |
| assertEquals("""{"state1":"foo","state2":"foo"}""", msgFull) |
| assertEquals("""{"state1":"foo"}""", Json.encodeToString(Base1.serializer(), d)) |
| val restored = Json.decodeFromString(Derived2.serializer(), msgFull) |
| val restored2 = |
| Json.decodeFromString(Derived2.serializer(), """{"state1":"bar","state2":"foo"}""") // state1 is ignored anyway |
| assertEquals("""Derived2(state1='foo')""", restored.toString()) |
| assertEquals("""Derived2(state1='foo')""", restored2.toString()) |
| } |
| |
| @Test |
| fun withoutModules() = assertStringFormAndRestored( |
| expected = """{"data":{"stringKey":["kotlin.String","string1"],"mapKey":["kotlin.collections.HashMap",[["kotlin.String","nestedKey"],["kotlin.String","nestedValue"]]],"listKey":["kotlin.collections.ArrayList",[["kotlin.String","foo"]]]}}""", |
| original = MyPolyData( |
| linkedMapOf( |
| "stringKey" to "string1", |
| "mapKey" to hashMapOf("nestedKey" to "nestedValue"), |
| "listKey" to listOf("foo") |
| ) |
| ), |
| serializer = MyPolyData.serializer(), |
| format = Json { useArrayPolymorphism = true; allowStructuredMapKeys = true } |
| ) |
| |
| @Suppress("NAME_SHADOWING") |
| private fun checkNotRegisteredMessage(className: String, scopeName: String, exception: SerializationException) { |
| val expectedText = |
| "is not registered for polymorphic serialization in the scope of" |
| assertEquals(true, exception.message?.contains(expectedText)) |
| } |
| |
| @Test |
| fun failWithoutModulesWithCustomClass() { |
| checkNotRegisteredMessage( |
| "sample.IntData", "kotlin.Any", |
| assertFailsWith<SerializationException>("not registered") { |
| Json.encodeToString( |
| MyPolyData.serializer(), |
| MyPolyData(mapOf("a" to IntData(42))) |
| ) |
| } |
| ) |
| } |
| |
| @Test |
| fun testWithModules() { |
| val json = Json { |
| useArrayPolymorphism = true; serializersModule = SerializersModule { polymorphic(Any::class) { subclass(IntData::class) } } } |
| assertStringFormAndRestored( |
| expected = """{"data":{"a":["sample.IntData",{"intV":42}]}}""", |
| original = MyPolyData(mapOf("a" to IntData(42))), |
| serializer = MyPolyData.serializer(), |
| format = json |
| ) |
| } |
| |
| /** |
| * This test should fail because PolyDerived registered in the scope of PolyBase, not kotlin.Any |
| */ |
| @Test |
| fun failWithModulesNotInAnyScope() { |
| val json = Json { serializersModule = BaseAndDerivedModule } |
| checkNotRegisteredMessage( |
| "sample.PolyDerived", "kotlin.Any", |
| assertFailsWith<SerializationException> { |
| json.encodeToString( |
| MyPolyData.serializer(), |
| MyPolyData(mapOf("a" to PolyDerived("foo"))) |
| ) |
| } |
| ) |
| } |
| |
| private val baseAndDerivedModuleAtAny = SerializersModule { |
| polymorphic(Any::class) { |
| subclass(PolyDerived::class) |
| } |
| } |
| |
| |
| @Test |
| fun testRebindModules() { |
| val json = Json { useArrayPolymorphism = true; serializersModule = baseAndDerivedModuleAtAny } |
| assertStringFormAndRestored( |
| expected = """{"data":{"a":["sample.PolyDerived",{"id":1,"s":"foo"}]}}""", |
| original = MyPolyData(mapOf("a" to PolyDerived("foo"))), |
| serializer = MyPolyData.serializer(), |
| format = json |
| ) |
| } |
| |
| /** |
| * This test should fail because PolyDerived registered in the scope of kotlin.Any, not PolyBase |
| */ |
| @Test |
| fun failWithModulesNotInParticularScope() { |
| val json = Json { serializersModule = baseAndDerivedModuleAtAny } |
| checkNotRegisteredMessage( |
| "sample.PolyDerived", "sample.PolyBase", |
| assertFailsWith<SerializationException> { |
| json.encodeToString( |
| MyPolyDataWithPolyBase.serializer(), |
| MyPolyDataWithPolyBase(mapOf("a" to PolyDerived("foo")), PolyDerived("foo")) |
| ) |
| } |
| ) |
| } |
| |
| @Test |
| fun testBindModules() { |
| val json = Json { useArrayPolymorphism = true; serializersModule = (baseAndDerivedModuleAtAny + BaseAndDerivedModule) } |
| assertStringFormAndRestored( |
| expected = """{"data":{"a":["sample.PolyDerived",{"id":1,"s":"foo"}]},"polyBase":["sample.PolyDerived",{"id":1,"s":"foo"}]}""", |
| original = MyPolyDataWithPolyBase(mapOf("a" to PolyDerived("foo")), PolyDerived("foo")), |
| serializer = MyPolyDataWithPolyBase.serializer(), |
| format = json |
| ) |
| } |
| |
| @Test |
| fun geoTest() { |
| val deser = nonstrict.decodeFromString(GeoCoordinate.serializer(), """{"latitude":1.0,"longitude":1.0}""") |
| assertEquals(GeoCoordinate(1.0, 1.0), deser) |
| } |
| |
| @Test |
| fun geoTest2() { |
| val deser = nonstrict.decodeFromString(GeoCoordinate.serializer(), """{}""") |
| assertEquals(GeoCoordinate(0.0, 0.0), deser) |
| } |
| |
| @Test |
| fun geoTestValidation() { |
| assertFailsWith<IllegalArgumentException> { |
| nonstrict.decodeFromString(GeoCoordinate.serializer(), """{"latitude":-1.0,"longitude":1.0}""") |
| } |
| } |
| } |
| |
| inline fun <reified T : Any> assertStringFormAndRestored( |
| expected: String, |
| original: T, |
| serializer: KSerializer<T>, |
| format: StringFormat = Json, |
| printResult: Boolean = false |
| ) { |
| val string = format.encodeToString(serializer, original) |
| if (printResult) println("[Serialized form] $string") |
| assertEquals(expected, string) |
| val restored = format.decodeFromString(serializer, string) |
| if (printResult) println("[Restored form] $original") |
| assertEquals(original, restored) |
| } |