Рекурсивно фильтровать и отображать список свойств - PullRequest
1 голос
/ 15 января 2020

Я использую Kotlin отражение, чтобы проверить, являются ли атрибуты, которые имеют определенную аннотацию, нулевыми.

Учитывая следующий пример:

data class DataClass(
    @SomeRandomAnnotation
    val otherAnnotated: String?,
    val inner: InnerClass
)

data class AnotherDataClass(
    @SomeRandomAnnotation
    val annotatedProperty: String?,
    val dataClass: DataClass
) {

    fun checkCreditAnalysisConstrain() {
        print(checkConstrain(this))
    }
}

И функция, которая проверяет это:

fun checkConstrain(parentClass: Any): List<String> {
    val filter = parentClass::class.memberProperties.filter {
        if (memberIsDataClass(it)) checkConstrain(getMemberPropertyInstance(parentClass, it))

        hasAnnotation(it) && propertyIsNull(it, parentClass)
    }
    return filter.map { formatResult(parentClass, it) }
}

Идея состоит в том, что функция будет перебирать атрибуты моих классов, проверяя, есть ли у них аннотация, и проверяя, является ли значение нулевым. Если свойство является классом данных, код рекурсивно оценивает свойства дочерних элементов.

После этого я отображаю результаты, превращая KProperty в простую строку, удобную для чтения человеком, содержащую имя класса и имя атрибута.

Проблема в том, что приведенный выше код не работает должным образом. Возвращаемые свойства - это только свойства класса первого уровня.

Если вместо фильтра я просто запускаю forEach и печатаю результат, я получаю ожидаемые атрибуты. Так что я уверен, что это связано с повторением внутри фильтра.

Видите ли вы какой-нибудь способ сделать это более функциональным способом? Я просто обеспокоен тем, что мне не понадобится список «temp», и я добавлю значения в список, а затем сброслю его.

1 Ответ

2 голосов
/ 15 января 2020

Ваша функция рекурсивно вызывает себя, но ничего не делает с возвращенным списком этого рекурсивного вызова. Вот почему вы получаете результаты только для класса верхнего уровня.

Кроме того, по моему мнению, вы не должны полагаться на побочные эффекты, возникающие при вашем filter звонке. Возможно, это работает, но документация функции не дает гарантии, что она будет вызываться ровно один раз для каждого элемента в коллекции. Поэтому для рекурсивных вызовов должен быть отдельный for-l oop, и результат должен быть добавлен к существующим результатам.

fun checkConstrain(parent: Any): List<String> {
    val memberProperties = parent::class.memberProperties
    var result = memberProperties
        .filter { hasAnnotation(it) && propertyIsNull(it, parent) }
        .map { formatResult(parent, it) }
    memberProperties.filter { memberIsDataClass(it) }
        .mapNotNull { getMemberPropertyInstance(parent, it) }
        .forEach { result += checkConstrain(it) }
    return result
}

Вы не предоставили код для некоторых функций, которые использовали , Вот что я использовал для них:

val KProperty<*>.returnTypeClass get() = this.returnType.classifier as? KClass<*>
fun <T> memberIsDataClass(member: KProperty<T>) = member.returnTypeClass?.isData == true
fun <T> getMemberPropertyInstance(parent: Any, property: KProperty<T>) = property.getter.call(parent)
fun <T> hasAnnotation(property: KProperty<T>) = property.annotations.firstOrNull { it.annotationClass == SomeRandomAnnotation::class } != null
fun <T> propertyIsNull(property: KProperty<T>, parent: Any) = getMemberPropertyInstance(parent, property) == null
fun formatResult(parent: Any, property: KProperty<*>) = "$parent's property(${property.name}) is annotated with SomeRandomAnnotation and is null."
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...