Property include/exclude on Kotlin data classes - kotlin

Suppose I only want one or two fields to be included in the generated equals and hashCode implementations (or perhaps exclude one or more fields). For a simple class, e.g.:
data class Person(val id: String, val name: String)
Groovy has this:
#EqualsAndHashCode(includes = 'id')
Lombok has this:
#EqualsAndHashCode(of = "id")
What is the idiomatic way of doing this in Kotlin?
My approach so far
data class Person(val id: String) {
// at least we can guarantee it is present at access time
var name: String by Delegates.notNull()
constructor(id: String, name: String): this(id) {
this.name = name
}
}
Just feels wrong though... I don't really want name to be mutable, and the extra constructor definition is ugly.

I've used this approach.
data class Person(val id: String, val name: String) {
override fun equals(other: Person) = EssentialData(this) == EssentialData(other)
override fun hashCode() = EssentialData(this).hashCode()
override fun toString() = EssentialData(this).toString().replaceFirst("EssentialData", "Person")
}
private data class EssentialData(val id: String) {
constructor(person: Person) : this(id = person.id)
}

This approach may be suitable for property exclusion:
class SkipProperty<T>(val property: T) {
override fun equals(other: Any?) = true
override fun hashCode() = 0
}
SkipProperty.equals simply returns true, which causes the embeded property to be skipped in equals of parent object.
data class Person(
val id: String,
val name: SkipProperty<String>
)

I also don't know "the idomatic way" in Kotlin (1.1) to do this...
I ended up overriding equals and hashCode:
data class Person(val id: String,
val name: String) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other?.javaClass != javaClass) return false
other as Person
if (id != other.id) return false
return true
}
override fun hashCode(): Int {
return id.hashCode()
}
}
Isn't there a "better" way?

This builds on #bashor's approach and uses a private primary and a public secondary constructor. Sadly the property to be ignored for equals cannot be a val, but one can hide the setter, so the result is equivalent from an external perspective.
data class ExampleDataClass private constructor(val important: String) {
var notSoImportant: String = ""
private set
constructor(important: String, notSoImportant: String) : this(important) {
this.notSoImportant = notSoImportant
}
}

Here's a somewhat creative approach:
data class IncludedArgs(val args: Array<out Any>)
fun includedArgs(vararg args: Any) = IncludedArgs(args)
abstract class Base {
abstract val included : IncludedArgs
override fun equals(other: Any?) = when {
this identityEquals other -> true
other is Base -> included == other.included
else -> false
}
override fun hashCode() = included.hashCode()
override fun toString() = included.toString()
}
class Foo(val a: String, val b : String) : Base() {
override val included = includedArgs(a)
}
fun main(args : Array<String>) {
val foo1 = Foo("a", "b")
val foo2 = Foo("a", "B")
println(foo1 == foo2) //prints "true"
println(foo1) //prints "IncludedArgs(args=[a])"
}

Reusable solution: to have an easy way to select which fields to include in equals() and hashCode(), I wrote a little helper called "stem" (essential core data, relevant for equality).
Usage is straightforward, and the resulting code very small:
class Person(val id: String, val name: String) {
private val stem = Stem(this, { id })
override fun equals(other: Any?) = stem.eq(other)
override fun hashCode() = stem.hc()
}
It's possible to trade off the backing field stored in the class with extra computation on-the-fly:
private val stem get() = Stem(this, { id })
Since Stem takes any function, you are free to specify how the equality is computed. For more than one field to consider, just add one lambda expression per field (varargs):
private val stem = Stem(this, { id }, { name })
Implementation:
class Stem<T : Any>(
private val thisObj: T,
private vararg val properties: T.() -> Any?
) {
fun eq(other: Any?): Boolean {
if (thisObj === other)
return true
if (thisObj.javaClass != other?.javaClass)
return false
// cast is safe, because this is T and other's class was checked for equality with T
#Suppress("UNCHECKED_CAST")
other as T
return properties.all { thisObj.it() == other.it() }
}
fun hc(): Int {
// Fast implementation without collection copies, based on java.util.Arrays.hashCode()
var result = 1
for (element in properties) {
val value = thisObj.element()
result = 31 * result + (value?.hashCode() ?: 0)
}
return result
}
#Deprecated("Not accessible; use eq()", ReplaceWith("this.eq(other)"), DeprecationLevel.ERROR)
override fun equals(other: Any?): Boolean =
throw UnsupportedOperationException("Stem.equals() not supported; call eq() instead")
#Deprecated("Not accessible; use hc()", ReplaceWith("this.hc(other)"), DeprecationLevel.ERROR)
override fun hashCode(): Int =
throw UnsupportedOperationException("Stem.hashCode() not supported; call hc() instead")
}
In case you're wondering about the last two methods, their presence makes the following erroneous code fail at compile time:
override fun equals(other: Any?) = stem.equals(other)
override fun hashCode() = stem.hashCode()
The exception is merely a fallback if those methods are invoked implicitly or through reflection; can be argued if it's necessary.
Of course, the Stem class could be further extended to include automatic generation of toString() etc.

Simpler, faster, look at there, or into the Kotlin documentation.
https://discuss.kotlinlang.org/t/ignoring-certain-properties-when-generating-equals-hashcode-etc/2715/2
Only fields inside the primary constructor are taken into account to build automatic access methods like equals and so on. Do keep the meaningless ones outside.

Here is another hacky approach if you don't want to touch the data class.
You can reuse the entire equals() from data classes while excluding some fields.
Just copy() the classes with fixed values for excluded fields:
data class Person(val id: String,
val name: String)
fun main() {
val person1 = Person("1", "John")
val person2 = Person("2", "John")
println("Full equals: ${person1 == person2}")
println("equals without id: ${person1.copy(id = "") == person2.copy(id = "")}")
}
Output:
Full equals: false
equals without id: true

Consider the following generic approach for the implementation of equals/hashcode. The code below should have no performance impact because of the use of inlining and kotlin value classes:
#file:Suppress("EXPERIMENTAL_FEATURE_WARNING")
package org.beatkit.common
import kotlin.jvm.JvmInline
#Suppress("NOTHING_TO_INLINE")
#JvmInline
value class HashCode(val value: Int = 0) {
inline fun combineHash(hash: Int): HashCode = HashCode(31 * value + hash)
inline fun combine(obj: Any?): HashCode = combineHash(obj.hashCode())
}
#Suppress("NOTHING_TO_INLINE")
#JvmInline
value class Equals(val value: Boolean = true) {
inline fun combineEquals(equalsImpl: () -> Boolean): Equals = if (!value) this else Equals(equalsImpl())
inline fun <A : Any> combine(lhs: A?, rhs: A?): Equals = combineEquals { lhs == rhs }
}
#Suppress("NOTHING_TO_INLINE")
object Objects {
inline fun hashCode(builder: HashCode.() -> HashCode): Int = builder(HashCode()).value
inline fun hashCode(vararg objects: Any?): Int = hashCode {
var hash = this
objects.forEach {
hash = hash.combine(it)
}
hash
}
inline fun hashCode(vararg hashes: Int): Int = hashCode {
var hash = this
hashes.forEach {
hash = hash.combineHash(it)
}
hash
}
inline fun <T : Any> equals(
lhs: T,
rhs: Any?,
allowSubclasses: Boolean = false,
builder: Equals.(T, T) -> Equals
): Boolean {
if (rhs == null) return false
if (lhs === rhs) return true
if (allowSubclasses) {
if (!lhs::class.isInstance(rhs)) return false
} else {
if (lhs::class != rhs::class) return false
}
#Suppress("unchecked_cast")
return builder(Equals(), lhs, rhs as T).value
}
}
With this in place, you can easily implement/override any equals/hashcode implementation in a uniform way:
data class Foo(val title: String, val bytes: ByteArray, val ignore: Long) {
override fun equals(other: Any?): Boolean {
return Objects.equals(this, other) { lhs, rhs ->
this.combine(lhs.title, rhs.title)
.combineEquals { lhs.bytes contentEquals rhs.bytes }
// ignore the third field for equals
}
}
override fun hashCode(): Int {
return Objects.hashCode(title, bytes) // ignore the third field for hashcode
}
}

You can create an annotation that represents the exclusion of the property as #ExcludeToString or with #ToString(Type.EXCLUDE) parameters by defining enum.
And then using reflection format the value of the getToString().
#Target(AnnotationTarget.FIELD)
#Retention(AnnotationRetention.RUNTIME)
annotation class ExcludeToString
data class Test(
var a: String = "Test A",
#ExcludeToString var b: String = "Test B"
) {
override fun toString(): String {
return ExcludeToStringUtils.getToString(this)
}
}
object ExcludeToStringUtils {
fun getToString(obj: Any): String {
val toString = LinkedList<String>()
getFieldsNotExludeToString(obj).forEach { prop ->
prop.isAccessible = true
toString += "${prop.name}=" + prop.get(obj)?.toString()?.trim()
}
return "${obj.javaClass.simpleName}=[${toString.joinToString(", ")}]"
}
private fun getFieldsNotExludeToString(obj: Any): List<Field> {
val declaredFields = obj::class.java.declaredFields
return declaredFields.filterNot { field ->
isFieldWithExludeToString(field)
}
}
private fun isFieldWithExludeToString(field: Field): Boolean {
field.annotations.forEach {
if (it.annotationClass == ExcludeToString::class) {
return true
}
}
return false
}
}
GL
Gist

Related

Identifying relationships in Kotlin

Are nested classes a good way to model identifying relationships in Kotlin?
Requirements:
an issue can not be without a serno
a revocation can not be without an issue
an affirmation can not be without a revocation
Looks quite verbose:
class Have {
inner class Serno(val value: String) {
override fun toString(): String = "serno: $value"
fun nothing () = this#Have
inner class Issue(val value: String) {
override fun toString(): String = "issue: $value (${serno()})"
fun serno () = this#Serno
inner class Revocation(val value: String) {
override fun toString(): String = "revocation: $value (${issue()})"
fun issue () = this#Issue
inner class Affirmation(val value: String) {
override fun toString(): String = "affirmation: $value (${revocation()})"
fun revocation () = this#Revocation
}
}
}
}
}
val serno: Have.Serno = Have().Serno("123")
val issue: Have.Serno.Issue = serno.Issue("SUP-1")
val revocation: Have.Serno.Issue.Revocation = issue.Revocation("2020")
val affirmation: Have.Serno.Issue.Revocation.Affirmation = revocation.Affirmation("2022")
println(serno)
println(issue)
println(revocation)
println(affirmation)
println(serno == affirmation.revocation().issue().serno())
Is there a simpler way to achieve the same?
This would usually be achieved with simple non-null properties:
class Serno(val value: String) {
override fun toString(): String = "serno: $value"
}
class Issue(val value: String, val serno: Serno) {
override fun toString(): String = "issue: $value ($serno)"
}
class Revocation(val value: String, val issue: Issue) {
override fun toString(): String = "revocation: $value ($issue)"
}
class Affirmation(val value: String, val revocation: Revocation) {
override fun toString(): String = "affirmation: $value ($revocation)"
}
val serno = Serno("123")
val issue = Issue("SUP-1", serno)
val revocation = Revocation("2020", issue)
val affirmation = Affirmation("2022", revocation)
And if you're not strict on the toString format, you could even use the built-in toString of data classes, and simplify further:
data class Serno(val value: String)
data class Issue(val value: String, val serno: Serno)
data class Revocation(val value: String, val issue: Issue)
data class Affirmation(val value: String, val revocation: Revocation)

Data class toString name clash property / construction arg

Newby in Kotlin, I'm trying to implement a simple data class with constraint validation in fields.
This works great, but my solution is suboptimal since it exposes the private variables names defined in the class' definition in the toString representation, where I would prefer to have the properties:
data class MutablePointKt(private val _x: Int = 0, private val _y: Int = 0) {
private fun validatePositiveOrZero(some:Int) {
Validate.isTrue(some >= 0, "negative coordinate provided: $some")
}
var x: Int = 0
get() {
println(" > getting x: $field")
return field
}
set(value) {
validatePositiveOrZero(value)
field = value
}
var y: Int = 0
get() {
println(" > getting y: $field")
return field
}
set(value) {
validatePositiveOrZero(value)
field = value
}
init {
this.x = _x;
this.y = _y;
}
}
println(MutablePointKt(1, 2)) // prints "MutablePointKt(_x=1, _y=2)". how to print "MutablePointKt(x=1, y=2)" ?
Thank you !
EDIT:
I have a solution with
override fun toString(): String = ToStringBuilder.reflectionToString(this, KotlinToStringStyle()) and
class KotlinToStringStyle : ToStringStyle() {
private fun isFiltered(s: String?) = s?.startsWith("_") == true
override fun appendFieldStart(buffer: StringBuffer?, fieldName: String?) {
if (!isFiltered(fieldName))
super.appendFieldStart(buffer, fieldName)
}
override fun appendDetail(buffer: StringBuffer?, fieldName: String?, any: Any) {
if (!isFiltered(fieldName))
super.appendDetail(buffer, fieldName, any)
}
override fun appendFieldEnd(buffer: StringBuffer?, fieldName: String?) {
if (!isFiltered(fieldName))
super.appendFieldEnd(buffer, fieldName)
}
}
... but this is rather overkill, I would prefer a concise solution aka "the Kotlin way"

Kotlinx Serialization - Custom serializer to ignore null value

Let's say I'm having a class like:
#Serializable
data class MyClass(
#SerialName("a") val a: String?,
#SerialName("b") val b: String
)
Assume the a is null and b's value is "b value", then Json.stringify(MyClass.serializer(), this) produces:
{ "a": null, "b": "b value" }
Basically if a is null, I wanted to get this:
{ "b": "b value" }
From some research I found this is currently not doable out of the box with Kotlinx Serialization so I was trying to build a custom serializer to explicitly ignore null value. I followed the guide from here but couldn't make a correct one.
Can someone please shed my some light? Thanks.
You can use explicitNulls = false
example:
#OptIn(ExperimentalSerializationApi::class)
val format = Json { explicitNulls = false }
#Serializable
data class Project(
val name: String,
val language: String,
val version: String? = "1.3.0",
val website: String?,
)
fun main() {
val data = Project("kotlinx.serialization", "Kotlin", null, null)
val json = format.encodeToString(data)
println(json) // {"name":"kotlinx.serialization","language":"Kotlin"}
}
https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/json.md#explicit-nulls
Use encodeDefaults = false property in JsonConfiguration and it won't serialize nulls (or other optional values)
Try this (not tested, just based on adapting the example):
#Serializable
data class MyClass(val a: String?, val b: String) {
#Serializer(forClass = MyClass::class)
companion object : KSerializer<MyClass> {
override val descriptor: SerialDescriptor = object : SerialClassDescImpl("MyClass") {
init {
addElement("a")
addElement("b")
}
}
override fun serialize(encoder: Encoder, obj: MyClass) {
encoder.beginStructure(descriptor).run {
obj.a?.let { encodeStringElement(descriptor, 0, obj.a) }
encodeStringElement(descriptor, 1, obj.b)
endStructure(descriptor)
}
}
override fun deserialize(decoder: Decoder): MyClass {
var a: String? = null
var b = ""
decoder.beginStructure(descriptor).run {
loop# while (true) {
when (val i = decodeElementIndex(descriptor)) {
CompositeDecoder.READ_DONE -> break#loop
0 -> a = decodeStringElement(descriptor, i)
1 -> b = decodeStringElement(descriptor, i)
else -> throw SerializationException("Unknown index $i")
}
}
endStructure(descriptor)
}
return MyClass(a, b)
}
}
}
Since I was also struggling with this one let me share with you the solution I found that is per property and does not require to create serializer for the whole class.
class ExcludeIfNullSerializer : KSerializer<String?> {
override fun deserialize(decoder: Decoder): String {
return decoder.decodeString()
}
override val descriptor: SerialDescriptor
get() = PrimitiveSerialDescriptor("ExcludeNullString", PrimitiveKind.STRING)
override fun serialize(encoder: Encoder, value: String?) {
if (value != null) {
encoder.encodeString(value)
}
}
}
will work as expected with the following class
#Serializable
class TestDto(
#SerialName("someString")
val someString: String,
#SerialName("id")
#EncodeDefault(EncodeDefault.Mode.NEVER)
#Serializable(with = ExcludeIfNullSerializer::class)
val id: String? = null
)
Note the #EncodeDefault(EncodeDefault.Mode.NEVER) is crucial here in case you using JsonBuilder with encodeDefaults = true, as in this case the serialization library will still add the 'id' json key even if the value of id field is null unless using this annotation.
JsonConfiguration is deprecated in favor of Json {} builder since kotlinx.serialization 1.0.0-RC according to its changelog.
Now you have to code like this:
val json = Json { encodeDefaults = false }
val body = json.encodeToString(someSerializableObject)
As of now, for anyone seeing this pos today, default values are not serialized (see https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/basic-serialization.md#defaults-are-not-encoded-by-default)
So you simply add to set a default null value, and it will not be serialized.

Best way to implement a Reassignable Property Delegate

I impemented Reassignable Property Delegate
internal object UNINITIALIZED_VALUE
class SynchronizedReassignableImpl<out T>(private val initializer: () -> T,
private val expiredPredicate: (T) -> Boolean,
lock: Any? = null) : Reassignable<T> {
#Volatile
private var _value: Any? = UNINITIALIZED_VALUE
private val lock = lock ?: this
override val value: T
get() {
if (!isExpired()) {
#Suppress("UNCHECKED_CAST") (_value as T)
}
return synchronized(lock) {
val _v2 = _value
#Suppress("UNCHECKED_CAST")
if (_v2 !== UNINITIALIZED_VALUE && !expiredPredicate.invoke(_value as T)) {
_v2 as T
} else {
val typedValue = initializer()
_value = typedValue
typedValue
}
}
}
#Suppress("UNCHECKED_CAST")
override fun isExpired(): Boolean = !isInitialized() || expiredPredicate.invoke(_value as T)
override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE
override fun toString(): String = if (isInitialized()) value.toString() else "Reassignable value not initialized yet."
operator fun getValue(any: Any, property: KProperty<*>): T = value
operator fun getValue(any: Nothing?, property: KProperty<*>): T = value
}
fun <T> reassignable(initializer: () -> T, expiredPredicate: (T) -> Boolean, lock: Any? = null): SynchronizedReassignableImpl<T> {
return SynchronizedReassignableImpl(initializer, expiredPredicate, lock)
}
interface Reassignable<out T> {
val value: T
fun isInitialized(): Boolean
fun isExpired(): Boolean
}
This code declares the Delegate Property is working like lazy but on each getter's call, the predicate will be invoked to define a state of value (is expired or not). If the value is expired the one will be reassigned.
It's working, for example
class SynchronizedReassignableImplTests {
#Test
fun isReassignable() {
val initializer = { mutableListOf<String>() }
val expiredPredicate = { l: List<String> -> l.size == 2 }
val list by reassignable(initializer, expiredPredicate)
Assertions.assertEquals(0, list.size)
list.add("item ${list.size}")
Assertions.assertEquals(1, list.size)
list.add("item ${list.size}") // list size is 2 on next getter's call it will be reassigned
Assertions.assertEquals(0, list.size)
list.add("item ${list.size}")
Assertions.assertEquals(1, list.size)
}
}
but I'm working with Kotlin for only two days and think my solution not so beautiful.
Can somebody give me the advice to do this? Or maybe Kotlin has a native solution?

Kotlin type auto boxing vs primitive

class Remember private constructor() {
private var data: ConcurrentMap<String, Any> = ConcurrentHashMap()
private fun <T> saveValue(key: String, value: T): Remember {
data[key] = value
return this
}
private fun <T> getValue(key: String, clazz: Class<T>): T? {
val value = data[key]
var castedObject: T? = null
//Failed here
if (clazz.isInstance(value)) {
castedObject = clazz.cast(value)
}
return castedObject
}
fun putInt(key: String, value: Int): Remember {
return saveValue(key, value)
}
fun getInt(key: String, fallback: Int): Int {
val value = getValue(key, Int::class.java)
return value ?: fallback
}
}
When I putInt(key, 123), 123 is autoboxed to java.lang.Integer. When I get value from the Map, how do I compare value typed Any with Class<T> in which T is Int:class.java in this case? Currently, clazz.isInstance(value) always fails. It works if this class is written in Java
I think that's not kotlin but Java. Map only accepts Object type. So the primitive type will be autoboxed to put in a Map. So value returns from Map is alway Object.