Skip to content

Instantly share code, notes, and snippets.

@mirceanis
Last active July 23, 2019 12:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mirceanis/21cf02419c9c8ceb7561c138fcdeead8 to your computer and use it in GitHub Desktop.
Save mirceanis/21cf02419c9c8ceb7561c138fcdeead8 to your computer and use it in GitHub Desktop.
(de)serialization for arbitrary trees using kotlinx.serialization
@file:Suppress("MemberVisibilityCanBePrivate", "EXPERIMENTAL_API_USAGE")
import kotlinx.serialization.ImplicitReflectionSerializer
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.*
import kotlinx.serialization.modules.getContextualOrDefault
import org.junit.Test
class ArbitrarySerializationTests {
/////////////////////
// converting to Json
/////////////////////
@ImplicitReflectionSerializer
fun Map<*, *>.toJsonObject(): JsonObject = JsonObject(map {
it.key.toString() to it.value.toJsonElement()
}.toMap())
@ImplicitReflectionSerializer
fun Any?.toJsonElement(): JsonElement = when (this) {
null -> JsonNull
is Number -> JsonPrimitive(this)
is String -> JsonPrimitive(this)
is Boolean -> JsonPrimitive(this)
is Map<*, *> -> this.toJsonObject()
is Iterable<*> -> JsonArray(this.map { it.toJsonElement() })
is Array<*> -> JsonArray(this.map { it.toJsonElement() })
else -> {
//supporting classes that declare serializers
val jsonParser = Json(JsonConfiguration.Stable)
val serializer = jsonParser.context.getContextualOrDefault(this)
jsonParser.toJson(serializer, this)
}
}
////////////////////////////////////
// converting back to primitives (no type info)
////////////////////////////////////
fun JsonObject.toPrimitiveMap(): Map<String, Any?> =
this.content.map {
it.key to it.value.toPrimitive()
}.toMap()
fun JsonElement.toPrimitive(): Any? = when (this) {
is JsonNull -> null
is JsonObject -> this.toPrimitiveMap()
is JsonArray -> this.map { it.toPrimitive() }
is JsonLiteral -> {
if (isString) {
contentOrNull
} else {
booleanOrNull ?: longOrNull ?: doubleOrNull
}
}
else -> null
}
/////////////
// data
/////////////
@Serializable
data class ClassWithSerializer(
@SerialName("@context")
val context: List<String>
)
val exampleMap = mapOf(
"hello" to "world",
"missing" to null,
"some number" to 4321,
"number as string" to "1234",
"boolean" to false,
"boolean as string" to "true",
"custom object" to ClassWithSerializer(listOf("asdf")),
"obj" to mapOf(
"a" to "b",
"c" to null
)
)
//language=json
val expectedSerialized =
"""{"hello":"world","missing":null,"some number":4321,"number as string":"1234","boolean":false,"boolean as string":"true","custom object":{"@context":["asdf"]},"obj":{"a":"b","c":null}}"""
val expectedDeserialized = mapOf(
"hello" to "world",
"missing" to null,
"some number" to 4321L,
"number as string" to "1234",
"boolean" to false,
"boolean as string" to "true",
"custom object" to mapOf("@context" to listOf("asdf")),
"obj" to mapOf(
"a" to "b",
"c" to null
)
)
@ImplicitReflectionSerializer
@Test
fun `can serialize arbitrary map`() {
val serialized = Json.stringify(JsonObjectSerializer, exampleMap.toJsonObject())
assert(serialized == expectedSerialized)
}
@ImplicitReflectionSerializer
@Test
fun `can deserialize arbitrary json`() {
val deserialized = Json.nonstrict.parseJson(expectedSerialized).jsonObject.toPrimitiveMap()
println("map: $deserialized")
assert(deserialized == expectedDeserialized)
}
}
@mirceanis
Copy link
Author

mirceanis commented Jul 18, 2019

The problem with this is that during deserialization the primitives lose their type.
Solve by using JsonLiteral instead of JsonPrimitive and checking for isString

( thanks to @emartynov )

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment