Skip to content

Instantly share code, notes, and snippets.

@0x1b-xyz
Created September 20, 2023 14:45
Show Gist options
  • Save 0x1b-xyz/504d16390a254a7e1cf46bfde2596ad2 to your computer and use it in GitHub Desktop.
Save 0x1b-xyz/504d16390a254a7e1cf46bfde2596ad2 to your computer and use it in GitHub Desktop.
Deep merge and deep get on kotlin `Map<String,Any>` maps
import java.util.*
import kotlin.reflect.KClass
/**
* Produces a new map that represents a merge of [source] into [this], returning an immutable view. Map leaves will be
* overwritten from [source] into [this].
*
* @param source Nested [Map<String,Any>] that will be merged over [this]. Must be [Map<String,Any>]
*
* @throws IllegalArgumentException When [source] causes a [ClassCastException] caused by a non-[String] key
*/
fun Map<String, Any>.deepMerge(source: Map<String, Any>): Map<String, Any> =
deepMerge(Stack<String>(), source)
fun Map<String, Any>.deepMerge(path: Stack<String>, source: Map<String, Any>): Map<String, Any> {
return this.toMutableMap().let { destination ->
try {
for (key in source.keys) {
if (source[key] is Map<*, *> && destination[key] is Map<*, *>) {
@Suppress("UNCHECKED_CAST")
destination[key] = (destination[key] as Map<String, Any>).deepMerge(
path.also { it.push(key) },
(source[key] as Map<String, Any>)
)
} else {
destination[key] = source[key] as Any
}
path.clear()
}
} catch (e: ClassCastException) {
throw IllegalArgumentException("Cannot deepMerge source['${if (path.empty()) "" else path.joinToString(".")}'] when keys are not Strings ...")
}
Collections.unmodifiableMap(destination)
}
}
/**
* Retrieves a value from a nested map using a dot-notated path.
*
* @param key Dot-notated path to the value retrieved
*
* @throws IllegalArgumentException When navigation fails because value is _null_ or we encounter a non
* [Map<String,Any>] entity in the path
*/
inline fun <reified R : Any> Map<String, Any>.deepGet(key: String): R =
deepGet(Stack<String>().also { it.addAll(key.split(".").reversed()) }, key, R::class)
fun <R : Any> Map<String, Any>.deepGet(path: Stack<String>, key: String, type: KClass<R>): R {
val current = path.pop()
return if (path.empty()) {
try {
@Suppress("UNCHECKED_CAST")
this[current] as R
} catch (e: Exception) {
val failedAt = key.split(".").dropLast(path.size).joinToString(".")
throw IllegalArgumentException("Cannot deepGet key '$failedAt' as the value '${if (this[current] == null) "null" else this[current]!!::class.simpleName}' is not ${type.simpleName} ...")
}
} else {
val remaining = this[current]
if (remaining !is Map<*, *>) {
val failedAt = key.split(".").dropLast(path.size).joinToString(".")
throw IllegalArgumentException("Cannot deepGet key '$failedAt' as the value '${if (remaining == null) "null" else remaining::class.simpleName}' is not Map<String,Any> ...")
}
@Suppress("UNCHECKED_CAST")
(remaining as Map<String, Any>).deepGet(path, key, type)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment