kotlinadvanced

Annotations and Runtime Reflection

Work with Kotlin annotations and reflection: custom annotations, property inspection, and annotation processing.

kotlin
import kotlin.reflect.KClass
import kotlin.reflect.KMutableProperty
import kotlin.reflect.full.*
import kotlin.reflect.jvm.isAccessible

// Custom annotations
@Target(AnnotationTarget.PROPERTY)
@Retention(AnnotationRetention.RUNTIME)
annotation class JsonField(val name: String = "")

@Target(AnnotationTarget.PROPERTY)
@Retention(AnnotationRetention.RUNTIME)
annotation class Required

@Target(AnnotationTarget.PROPERTY)
@Retention(AnnotationRetention.RUNTIME)
annotation class MaxLength(val value: Int)

@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class Entity(val tableName: String = "")

// Annotated data class
@Entity(tableName = "users")
data class User(
    @JsonField("user_id")
    val id: Int,

    @Required
    @MaxLength(50)
    @JsonField("user_name")
    val name: String,

    @Required
    @JsonField
    val email: String,

    @MaxLength(500)
    val bio: String? = null
)

// Reflection-based JSON serializer
fun Any.toJsonMap(): Map<String, Any?> {
    val kClass = this::class
    return kClass.memberProperties.associate { prop ->
        prop.isAccessible = true
        val jsonField = prop.findAnnotation<JsonField>()
        val key = if (jsonField != null && jsonField.name.isNotEmpty()) {
            jsonField.name
        } else {
            prop.name
        }
        key to prop.getter.call(this)
    }
}

// Reflection-based validator
fun Any.validate(): List<String> {
    val errors = mutableListOf<String>()
    val kClass = this::class

    for (prop in kClass.memberProperties) {
        prop.isAccessible = true
        val value = prop.getter.call(this)

        // Check @Required
        if (prop.findAnnotation<Required>() != null) {
            if (value == null || (value is String && value.isBlank())) {
                errors.add("${prop.name} is required")
            }
        }

        // Check @MaxLength
        prop.findAnnotation<MaxLength>()?.let { maxLen ->
            if (value is String && value.length > maxLen.value) {
                errors.add("${prop.name} exceeds max length of ${maxLen.value}")
            }
        }
    }
    return errors
}

// Reflection utilities
fun <T : Any> inspectClass(kClass: KClass<T>) {
    println("=== ${kClass.simpleName} ===")

    // Class annotations
    kClass.annotations.forEach { ann ->
        println("  @${ann.annotationClass.simpleName}")
        if (ann is Entity) println("    table: ${ann.tableName}")
    }

    // Properties
    println("  Properties:")
    kClass.memberProperties.forEach { prop ->
        val annotations = prop.annotations.map { it.annotationClass.simpleName }
        val mutable = if (prop is KMutableProperty<*>) "var" else "val"
        println("    $mutable ${prop.name}: ${prop.returnType} $annotations")
    }

    // Constructors
    println("  Constructors:")
    kClass.constructors.forEach { ctor ->
        val params = ctor.parameters.map { "${it.name}: ${it.type}" }
        println("    (${params.joinToString(", ")})")
    }
}

// Dynamic object creation
fun <T : Any> createInstance(kClass: KClass<T>, args: Map<String, Any?>): T {
    val ctor = kClass.primaryConstructor
        ?: throw IllegalArgumentException("No primary constructor")
    val params = ctor.parameters.associateWith { param ->
        args[param.name] ?: if (param.isOptional) null
        else throw IllegalArgumentException("Missing: ${param.name}")
    }.filterValues { it != null || true }
    return ctor.callBy(params.filter { !it.key.isOptional || it.value != null })
}

fun main() {
    val user = User(1, "Alice", "alice@test.com", "Developer")

    // Serialize
    println("JSON map: ${user.toJsonMap()}")

    // Validate
    val valid = user.validate()
    println("\nValidation: ${if (valid.isEmpty()) "PASS" else valid}")

    val invalid = User(2, "", "", "x".repeat(600))
    println("Invalid: ${invalid.validate()}")

    // Inspect
    println()
    inspectClass(User::class)

    // Dynamic creation
    val dynamic = createInstance(User::class, mapOf(
        "id" to 3,
        "name" to "Bob",
        "email" to "bob@test.com"
    ))
    println("\nDynamic: $dynamic")
}

Use Cases

  • Custom annotation-driven validation
  • Reflection-based serialization
  • Dynamic object creation and inspection

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.