How to use dynamic string substitution in Kotlin?

I'm looking for a Kotlin way to do a dynamic values substitution into a string. It is clear how to implement it, just want to check if there is something similar in standard library.

Could you help me to find a function which given template and data map returns a resulting string with all template keys replaced with their values?

fun format(template: String, data: Map<String, Any>): String { /* magic */ }

format("${a} ${b} ${a}", mapOf("a" to "Home", "b" to "Sweet))   // -> "Home Sweet Home"

3 answers

  • answered 2022-01-19 17:31 lukas.j

    fun format(template: String, data: Map<String, String>): String {
      var retval = template
      data.forEach { dataEntry ->
        retval = retval.replace("\${" + dataEntry.key + "}", dataEntry.value)
      }
      return retval
    }
    
    // The $ signs in the template string need to be escaped to prevent
    // string interpolation
    format("\${a} \${b} \${a}", mapOf("a" to "Home", "b" to "Sweet"))
    

  • answered 2022-01-19 19:08 Emanuel Moecklin

    Not shorter than lukas.j's answer, just different (using Regex):

    val regex = "\\\$\\{([a-z])}".toRegex()
    
    fun format(template: String, data: Map<String, String>) =
        regex.findAll(template).fold(template) { result, matchResult ->
            val (match, key) = matchResult.groupValues
            result.replace(match, data[key] ?: match)
        }
    

  • answered 2022-01-22 14:43 diziaq

    I did not find any thing standard to solve the problem.

    So here is a balanced (readability/performance/extensibility) solution also handling cases when some substitutions are undefined in dataMap.

    makeString("\${a} # \${b} @ \${c}", mapOf("a" to 123, "c" to "xyz"))   // => "123 # ??? @ xyz"
    

    --

    object Substitutions {
        private val pattern = Pattern.compile("\\$\\{([^}]+)\\}")
    
        fun makeString(
            template: String, 
            dataMap: Map<String, Any?>, 
            undefinedStub: String = "???"
        ): String {
            val replacer = createReplacer(dataMap, undefinedStub)
            val messageParts = splitWithDelimiters(template, pattern, replacer)
            return messageParts.joinToString("")
        }
    
        private fun createReplacer(dataMap: Map<String, Any?>, stub: String): (Matcher) -> String {
            return { m ->
                val key = m.group(1)
                (dataMap[key] ?: stub).toString()
            }
        }
    
        private fun splitWithDelimiters(
            text: String,
            pattern: Pattern,
            matchTransform: (Matcher) -> String
        ): List<String> {
            var lastMatch = 0
            val items = mutableListOf<String>()
            val m = pattern.matcher(text)
    
            while (m.find()) {
                items.add(text.substring(lastMatch, m.start()))
                items.add(matchTransform(m))
                lastMatch = m.end()
            }
    
            items.add(text.substring(lastMatch))
    
            return items
        }
    }
    

How many English words
do you know?
Test your English vocabulary size, and measure
how many words do you know
Online Test
Powered by Examplum