I'm trying to get all member properties' JsonPath in kotlin.
Following is my data class with annotated fields
class Test(
@Mask val testField: String?,
val nested: NestedTest,
val nestedList: List<NestedTest>?,
val generic: GenericTest<NestedGenericTest>
)
class NestedTest(
@Mask val test2: String = "",
val nestedNestedTest: NestedNestedTest = NestedNestedTest()
)
class NestedNestedTest(
@Mask
val test3: String = ""
)
class GenericTest<T>(
val generic: T
)
class NestedGenericTest(
@Mask
val nestedGeneric: String
)
H'm trying using kotlin-reflection, but I don't care what I use.
how can I get it?
Okay, so let's start with defining our annotation.
@Retention(AnnotationRetention.RUNTIME)
annotation class Mask
The important thing here is the retention, which must be runtime.
In my solution I am going to operate on Kotlin properties, so I need to slightly change the Annotation target:
// from
class Clazz(
@Mask val field: Field
)
// to
class Clazz(
@property:Mask val field: Field
)
What I need to do is to take all object properties and iterate over them (to find all marked fields).
At first lest define some types I am not going to get deeper. In my case, it's just a list of primitives and String:
val simpleTypes = setOf(
// define types you would like to stop iterating (like "basic types")
Byte::class,
Boolean::class,
Short::class,
Integer::class,
Long::class,
Float::class,
Double::class,
Char::class,
String::class,
)
So, in my function, I am going to return a property definition with its value:
typealias KPropertyWithValue = Pair<KProperty1<out Any, *>, Any?>
To finally get my functions:
fun getMaskedFromObject(v: Any?): List<KPropertyWithValue> {
// early returns - support for some special types, like Collection, Arrays etc.
when {
v == null -> return emptyList()
v is Collection<*> -> return v.flatMap { getMaskedFromObject(it) }
v is Array<*> -> return v.flatMap { getMaskedFromObject(it) }
// support for other types (Streams, custom collections etc.)
v::class in simpleTypes -> return emptyList()
}
v!! // just a mark the v is not null at this point - kotlin compiler is not handling the first when/return properly
val properties = v::class.memberProperties
val annotated = properties.filter { it.annotations.any { ann -> ann is Mask } }
val propertyValues = properties.map { it.getter.call(v) }
val annotatedWithValue = annotated.map {
val value = it.getter.call(v)
Pair(it, value)
}
return annotatedWithValue + propertyValues.flatMap { getMaskedFromObject(it) }
}
So, with a code like:
fun main() {
val test = Test(
testField = "root_testField",
nested = NestedTest(
test2 = "nested_test2",
nestedNestedTest = NestedNestedTest(
test3 = "nested_test3"
)
),
nestedList = listOf(
NestedTest(
test2 = "list_test2",
nestedNestedTest = NestedNestedTest(
test3 = "list_test3"
)
)
),
generic = GenericTest(
generic = NestedGenericTest(
nestedGeneric = "generic_nested"
)
)
)
val result = getMaskedFromObject(test)
result.forEach {
println(it)
}
}
The result is:
(val com.example.springsandbox.utils.Test.testField: kotlin.String?, root_testField)
(val com.example.springsandbox.utils.NestedGenericTest.nestedGeneric: kotlin.String, generic_nested)
(val com.example.springsandbox.utils.NestedTest.test2: kotlin.String, nested_test2)
(val com.example.springsandbox.utils.NestedNestedTest.test3: kotlin.String, nested_test3)
(val com.example.springsandbox.utils.NestedTest.test2: kotlin.String, list_test2)
(val com.example.springsandbox.utils.NestedNestedTest.test3: kotlin.String, list_test3)
Please notice a few things: