diff --git a/docs/json_and_kotlin_serialization.md b/docs/json_and_kotlin_serialization.md index f98c2d8..18ba131 100644 --- a/docs/json_and_kotlin_serialization.md +++ b/docs/json_and_kotlin_serialization.md @@ -98,6 +98,49 @@ formatting and parsing actual Json strings. This is why we use `Json.decodeFromJ `Json.decodeFromString`. Such an approach gives satisfactory performance without writing and supporting custom `kotlinx.serialization` codecs. +### Pitfall: JSON objects and Map + +Kotlin serialization does not support `Map` as a serializable type, more general, it can't serialize `Any`. This in particular means that you can deserialize Kotlin `Map` as long as `T` is `@Serializable` in Kotlin: + +```kotlin +@Serializable +data class TestJson2( + val value: Int, + val inner: Map +) + +@Test +fun deserializeMapWithJsonTest() = runTest { + val x = eval(""" + import lyng.serialization + { value: 1, inner: { "foo": 1, "bar": 2 }} + """.trimIndent()).decodeSerializable() + // That works perfectly well: + assertEquals(TestJson2(1, mapOf("foo" to 1, "bar" to 2)), x) +} +``` + +But what if your map has objects of different types? The approach of using polymorphism is partially applicable, but what to do with `{ one: 1, two: "two" }`? + +The answer is pretty simple: use `JsonObject` in your deserializable object. This class is capable of holding any JSON types and structures and is sort of a silver bullet for such cases: + +~~~kotlin +@Serializable +data class TestJson3( + val value: Int, + val inner: JsonObject +) +@Test +fun deserializeAnyMapWithJsonTest() = runTest { + val x = eval(""" + import lyng.serialization + { value: 12, inner: { "foo": 1, "bar": "two" }} + """.trimIndent()).decodeSerializable() + assertEquals(TestJson3(12, JsonObject(mapOf("foo" to JsonPrimitive(1), "bar" to Json.encodeToJsonElement("two")))), x) +} +~~~ + + # List of supported types | Lyng type | JSON type | notes | diff --git a/lynglib/src/commonTest/kotlin/ScriptTest.kt b/lynglib/src/commonTest/kotlin/ScriptTest.kt index c9471c2..6facf19 100644 --- a/lynglib/src/commonTest/kotlin/ScriptTest.kt +++ b/lynglib/src/commonTest/kotlin/ScriptTest.kt @@ -26,6 +26,10 @@ import kotlinx.coroutines.withTimeout import kotlinx.datetime.Clock import kotlinx.datetime.Instant import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.encodeToJsonElement import net.sergeych.lyng.* import net.sergeych.lyng.obj.* import net.sergeych.lyng.pacman.InlineSourcesImportProvider @@ -3962,4 +3966,33 @@ class ScriptTest { """.trimIndent() ) } + + @Serializable + data class TestJson2( + val value: Int, + val inner: Map + ) + + @Test + fun deserializeMapWithJsonTest() = runTest { + val x = eval(""" + import lyng.serialization + { value: 1, inner: { "foo": 1, "bar": 2 }} + """.trimIndent()).decodeSerializable() + assertEquals(TestJson2(1, mapOf("foo" to 1, "bar" to 2)), x) + } + + @Serializable + data class TestJson3( + val value: Int, + val inner: JsonObject + ) + @Test + fun deserializeAnyMapWithJsonTest() = runTest { + val x = eval(""" + import lyng.serialization + { value: 12, inner: { "foo": 1, "bar": "two" }} + """.trimIndent()).decodeSerializable()println(x) + assertEquals(TestJson3(12, JsonObject(mapOf("foo" to JsonPrimitive(1), "bar" to Json.encodeToJsonElement("two")))), x) + } }