ITNEXT

ITNEXT is a platform for IT developers & software engineers to share knowledge, connect, collaborate, learn and experience next-gen technologies.

Follow publication

AnyMap Deep Merge with Kotlin

3 min readMar 15, 2023
Image created by the author

Practical need

@POST
@Consumes(MediaType.APPLICATION_JSON)
fun createUser(
data: Map<String, Any>
)

Method definition

typealias AnyMap = Map<String, Any>
val Any?.asAnyMap: AnyMap
get() {
val resultMap = mutableMapOf<String, Any>()
if (this is Map<*, *>) {
for (item in this) {
val key = item.key
val value = item.value
if (key != null && value != null) {
if (key is String) {
resultMap[key] = value
} else {
throw RuntimeException("Expected Map<String,Any> but was Map<${key::class.simpleName},${value::class.simpleName}>")
}
}
}
} else {
val typeName = if (this == null) "null" else this::class.simpleName
throw RuntimeException("Expected Map<*,*> but was $typeName")
}

return resultMap
}
private fun deepMerge(destination: AnyMap, source: AnyMap): AnyMap {
val resultMap = destination.toMutableMap()
for (key in source.keys) {
//recursive merge for nested maps
if (source[key] is Map<*, *> && resultMap[key] is Map<*, *>) {
val originalChild = resultMap[key].asAnyMap
val newChild = source[key].asAnyMap
resultMap[key] = deepMerge(originalChild, newChild)
//merge for collections
} else if (source[key] is Collection<*> && resultMap[key] is Collection<*>) {
if (!(resultMap[key] as Collection<*>).containsAll(source[key] as Collection<*>)) {
resultMap[key] = (resultMap[key] as Collection<*>) + (source[key] as Collection<*>)
}
} else {
if (source[key] == null || (source[key] is String && (source[key] as String).isBlank())) continue
resultMap[key] = source[key] as Any
}
}
return resultMap
}

You should be aware that build in “+” operator does a shallow merge by default.

operator fun AnyMap.plus(source: AnyMap): AnyMap {
return deepMerge(this, source)
}

Usage

val map1 = mapOf(
"name" to "John",
"address" to mapOf(
"nums" to listOf(1, 2),
"line1" to 1,
"line2" to null
)
)

val map2 = mapOf(
"age" to 18,
"address" to mapOf(
"nums" to listOf(3),
"line2" to 2,
"line3" to 3
)
)
// BEFORE - shalow merge/replace, standart behaviour
print(map1 + map2)
//{name=John, address={nums=[3], line2=2, line3=3}, age=18}

// AFTER - deep merge, overridden ’+’
println(map1 + map2)
//{name=John, address={nums=[1, 2, 3], line1=1, line2=2, line3=3}, age=18}

Conclusion

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

ITNEXT
ITNEXT

Published in ITNEXT

ITNEXT is a platform for IT developers & software engineers to share knowledge, connect, collaborate, learn and experience next-gen technologies.

Vlad Mykol
Vlad Mykol

Written by Vlad Mykol

Full-time Software Engineer | Team Lead | Lover of playing my guitar | https://vladmykol.com/

Responses (1)

Write a response