1

E.g.:

data class Key(
    val id:          Int,
    val secret:      String,
    val description: String?
)

To exclude or mask special properties like passwords or credit card numbers:

Key(
    id          = 1,
    secret      = "foo",
    description = "bar"
).toString()
// Key(id=1, description=bar)
// or
// Key(id=1, secret=********, description=bar)

Or to ignore properties with nulls to make the resulting string more readable:

Key(
    id          = ...,
    secret      = ...,
    description = null
).toString()
// Key(id=...)
// or
// Key(id=..., secret=...)

Implementing toString() each time may be very tedious and error-prone, especially if you have too many properties in the class.

Is there any (upcoming) solution for this problem (e.g. like Lombok for Java)?

Viktor
  • 1,298
  • 15
  • 28
  • 4
    Possible duplicate of [How to extend a dataclass with toString](https://stackoverflow.com/questions/35970957/how-to-extend-a-dataclass-with-tostring) – Laksitha Ranasingha Feb 06 '19 at 10:06
  • @LaksithaRanasingha That question asks about using extension methods, which this one doesn't. And more importantly, doesn't ask how to minimize "tedious and error-prone" code in `toString()` implementation as this question does. Not a duplicate at all. – Alexey Romanov Feb 06 '19 at 11:30

3 Answers3

2

I reworked previous response by Andrii Vdovychenko as follows:

data class Key(val id: Int, val secret: String, val description: String?){
    override fun toString() = kotlinToString(target = this, properties = arrayOf(prop(Key::id), prop(Key::description), prop(Key::secret)))
}

fun <T>prop(kp : KProperty1<T, Any?>) : KProperty1<Any, Any?> {
    return kp as KProperty1<Any, Any?>
}

fun kotlinToString(target: Any, properties : Array<KProperty1<Any, Any?>>) : String {
    return properties
            .map { kp -> Pair(kp.name, kp.get(target)) }
            .filter { p -> p.second != null }
            .map { p -> "${p.first}: ${p.second}" }
            .joinToString(", ")
}

You can test mi implementation using the following main function:

fun main(args : Array<String>) {
    println(Key(1, "aa", "bbb").toString())
    println(Key(1, "aa", null).toString())
}

which output is

id: 1, description: bbb, secret: aa
id: 1, secret: aa

May be you can change kotlinToString implementation in order to include a per-class prefix in the output string, perhaps passing it as parameter to kotlinToString, but I hope this can helpo you!

Pietro Martinelli
  • 1,776
  • 14
  • 16
1

You can try something like this:

data class Key(val id: Int, val secret: String, val description: String?){ 
    override fun toString() = kotlinToString(properties = arrayOf(Key::id, Key::description)
} 

Example output:

0

The answer of Andrii Vdovychenko seems incomplete but inspired me to do this:

fun <T> kotlinToString(target: T, properties: Array<KProperty1<T, Any?>>): String {
    return properties
        .map { kp -> Pair(kp.name, kp.get(target)) }
        .filter { p -> p.second != null }
        .joinToString(", ") { p -> "${p.first}: ${p.second}" }
}

which seems to be a little clearer and can be used like this:

data class Key(val id: Int, val secret: String, val description: String?){
    override fun toString() = kotlinToString(target = this, properties = arrayOf(Key::id, Key::description, Key::secret)) 
}

Another Version could be using varargs which saves the "arrayOf" in the call:

fun <T> kotlinToString(target: T, vararg properties: KProperty1<T, Any?>): String {
    return properties
        .map { kp -> Pair(kp.name, kp.get(target)) }
        .filter { p -> p.second != null }
        .joinToString(", ") { p -> "${p.first}: ${p.second}" }
}
Loop
  • 480
  • 2
  • 9