I'm trying to figure out a way to serialize an arbitrary Map<String, Any> into JSON. In my case, I can guarantee that in runtime any value of the map is either a primitive, a list or a map. In case of lists and maps, the values of them are either primitives or lists or maps of the same pattern. Seems, it lays nicely on a json object.
I do not need to deserialize such an object, serialization only.
For now, I'm trying to write a custom serializer for such case but no success yet. How can it be done for both JVM and JS?
This can be done via using JsonElement, see #175 and #276.
This functionality has been implemented and will be shipped with next release.
@sandwwraith Can you possibly share an example code to deal with Map<String, Any>? 馃檱
@hotchemi You can replace Map type with JsonObject
@sandwwraith this isnt really a solution for orgs that already have maps heavily ingrained, which is more likely than having JsonObject everywhere. There should be some support for Map
JsonObject implements Map<String, JsonElement>. I believe that Any is not very type-safe and much more cumbersome to handle, but this JsonElement itself can be easily converted to Any recursively.
Its about converting Any to JsonElement, not the other way around. Any isnt very type-safe but provides a big use case with Maps. As with other serializers it would just thrown an JsonException on unsupported types.
@dri94 did you find a way to convert Any to JsonElement in order to convert a Map<String, Any> to JsonObject?
It appears there is no way to serialize a dashed variable in Kotlin JS, JSONObject only works on JVM: https://github.com/JetBrains/kotlin-wrappers/issues/339
ERROR: "name contains illegal identifiers that can't appear in javascript identifiers"
styleManager = jsObject{
clearProperties = true
}
plugins = arrayOf(
"grapesjs-lory-slider",
"grapesjs-tabs",
"grapesjs-custom-code",
"grapesjs-touch",
"grapesjs-parser-postcss",
"grapesjs-tooltip",
"grapesjs-tui-image-editor",
"grapesjs-typed",
"grapesjs-style-bg",
"grapesjs-preset-webpage"
//"grapesjs-plugin-filestack"
)
pluginsOpts = jsObject<dynamic> {
`grapesjs-lory-slider` = jsObject<dynamic> {
sliderBlock = jsObject<dynamic> {
category = "Extra"
}
}
}
EDIT/SOLVED: this[ "grapesjs-lory-slider"]
This is how I am doing Map to JsonElement for now. I take all primitive to be string (In my case it is fine).
fun List<*>.toJsonElement(): JsonElement {
val list: MutableList<JsonElement> = mutableListOf()
this.forEach {
val value = it as? Any ?: return@forEach
when(value) {
is Map<*, *> -> list.add((value).toJsonElement())
is List<*> -> list.add(value.toJsonElement())
else -> list.add(JsonPrimitive(value.toString()))
}
}
return JsonArray(list)
}
fun Map<*, *>.toJsonElement(): JsonElement {
val map: MutableMap<String, JsonElement> = mutableMapOf()
this.forEach {
val key = it.key as? String ?: return@forEach
val value = it.value ?: return@forEach
when(value) {
is Map<*, *> -> map[key] = (value).toJsonElement()
is List<*> -> map[key] = value.toJsonElement()
else -> map[key] = JsonPrimitive(value.toString())
}
}
return JsonObject(map)
}
I was expecting to be able to use Map<String, Serializable?> instead to get the proper serializer, but looks like this not working either.
No way to get a generic interface for all the possible serializable types?
Well to keep this more generic, I'm using a Map where all the elements have a serializer and we can use the kotlin reflection to get the right one for the handled type, so we can get an object out of the map with just:
fun buildJsonObject(other: Map<String, Any?>) : JsonElement {
val jsonEncoder = Json{ encodeDefaults = true } // Set this accordingly to your needs
val map = emptyMap<String, JsonElement>().toMutableMap()
other.forEach {
map[it.key] = if (it.value != null)
jsonEncoder.encodeToJsonElement(serializer(it.value!!::class.starProjectedType), it.value)
else JsonNull
}
return JsonObject(map)
}
And this will still throw a SerializationException in case the value type has not a serializer available.
However, I'm not still fully happy as I'd prefer some more generic serializable type so that can be used with binary when using CBOR serialization, and so where the encoding happens only at the moment we call the Json.encodeToString or Cbor.encodeToByteArray depending whether the serializer supports or not the binary format.
Not to mention that a such built object would just fail with Cbor (Got an error while parsing: java.lang.IllegalStateException: This serializer can be used only with Json format.Expected Encoder to be JsonEncoder, got class kotlinx.serialization.cbor.internal.CborMapWriter).
So, to handle the generic serializer case (such as binary ones), I've crafted some raw KSerializer for Any? that manually serializes the dynamic type of the element other than the value itself.
So basically mimicking what Polymorphic does, I'm not using any experimental or internal APIs but some of them could improve the result, like reusing the type serialName if any (even though, I'm not sure how i can deserialize that).
In the JSON case it could be probably optimized removing the type at all when using a Primitive one. (EDIT: this is done now)
Here's a gist, but suggestions are welcome: https://gist.github.com/3v1n0/ecbc5e825e2921bd0022611d7046690b
Most helpful comment
Its about converting Any to JsonElement, not the other way around. Any isnt very type-safe but provides a big use case with Maps. As with other serializers it would just thrown an JsonException on unsupported types.