Properties to JsonNode

How difficult do you find it to convert properties into a json object ?

It would require a lot of boilerplate work in java, but it is easier in kotlin.

This snippet reads a Map of properties paths and values and converts them into a json node (or a map of maps (of maps…) of values)
it can read dot separators as well as array indexes (a.b[2].c)

object PropertiesToJsonNode {

/*
a.b=1
a.c=3
c.d.e.f=2
c.d.g[0].h=3
c.d.g.1.h=4
=> {"a":{"b":1,"c":3},"c":{"d":{"e":{"f":2},"g":[{"h":3},{"h":4}]}}}

*/
fun from(map: Map<String, Any?>) =
map.split({key, value -> path(normalized(key), value)}).merge().listifyWhenPossible()

private fun Map<String, Any?>.split(splitFunction: (String, Any?) -> Map<String, Any?> ) : Array<Map<String, Any?>> =
this.entries.map { it.key to splitFunction(it.key, it.value) }.toMap().values.toTypedArray()

private fun normalized(key: String): String = key.replace(Regex("\\[([0-9]+)\\]\\."), ".$1.")

private fun Array<Map<String, Any?>>.merge() = this.reduce { acc, map -> mergeTwo(acc, map) }

private fun Map<String, Any?>.listifyWhenPossible() : Map<String, Any?> =
this.map { it.key to maybeTransformedToList(it.value)}.toMap()

@Suppress("UNCHECKED_CAST")
private fun maybeTransformedToList(value: Any?) = when {
value !is Map<*, *> -> value
(value as Map<String, Any?>).filterNot{ it.key.matches(Regex("^[0-9]+$"))}.isEmpty() ->
IntStream.rangeClosed(0, parseInt(value.keys.sortedDescending()[0])).boxed().map {
if(value["$it"] is Map<*, *>) (value["$it"] as Map<String, Any?>).listifyWhenPossible()
else value["$it"] }.toArray()
else -> value.listifyWhenPossible()
}

private fun path(key: String, value: Any?) =
nest(key.split(".").slice(1 until key.split(".").size), key.split(".")[0], value)

private fun nest(allOtherKeys: List<String>, currentKey: String, value: Any?) : Map<String, Any?> =
mapOf(Pair(currentKey, if(allOtherKeys.isEmpty()) value else
nest(allOtherKeys.slice(1 until allOtherKeys.size), allOtherKeys[0], value)))

private fun mergeTwo(first: Map<String, Any?>, second: Map<String, Any?>): Map<String, Any?> {
val presentInFirstOnly = first.keys.minus(second).map { it to first[it] }
val presentInSecondOnly = second.keys.minus(first).map { it to second[it]}
val intersection = first.keys.intersect(second.keys).map { it to mergeOneKey(first, second, it) }

return presentInFirstOnly.union( presentInSecondOnly).union(intersection).map { it.first.toString() to it.second}.toMap()
}

@Suppress("UNCHECKED_CAST")
private fun mergeOneKey(first: Map<String, Any?>, second: Map<String, Any?>, key: String)=
(if (first[key] is Map<*, *> && second[key] !is Map<*, *>) {first[key]} else
{if (first[key] !is Map<*, *> && second[key] is Map<*, *>) second[key] else
{
mergeTwo(first[key] as Map<String, Any>, second[key] as Map<String, Any>)
}})
}

Leave a Reply

Your email address will not be published. Required fields are marked *

*