kmapx¶
Mapper Kotlin-first en compile-time: todo mapeo inseguro es un error de compilación.
kmapx genera el código de mapeo entre tus clases (dominio ↔ DTO, entidad ↔ response, etc.)
durante la compilación, usando KSP. No hay
reflection, no hay strings con código embebido y no hay overhead en runtime: lo que se genera
son extensions e implementaciones en Kotlin real, que puedes abrir, leer, navegar y depurar en
el IDE como cualquier otro archivo tuyo. Cuando un mapeo no cierra — un campo sin fuente, un
String? intentando llenar un String, tipos incompatibles — el compilador te lo dice con un
código estable (KMXnnn), la línea exacta del campo culpable y un "did you mean" cuando hay
un candidato parecido.
@MapTo(PersonDto::class)
data class Person(val name: String, val age: Int)
// → fun Person.toPersonDto(): PersonDto, generado y verificado en compile-time
Requisitos¶
| Requisito | Versión |
|---|---|
| Kotlin | 2.1+ |
| KSP | 2.x (KSP2: ksp.useKSP2=true en gradle.properties) |
| JDK para el build | 17+ |
| Plataformas | JVM/Android, JS(IR), WasmJS, Linux x64, Windows x64, macOS, iOS |
Instalación rápida (JVM)¶
// build.gradle.kts
plugins {
kotlin("jvm") version "2.1.21"
id("com.google.devtools.ksp") version "2.1.21-2.0.1"
}
dependencies {
implementation("io.github.kuroxbyte:kmapx-annotations:0.1.0")
// Solo si usas converters calificados (Converts<A, B>) o Patch<T>:
implementation("io.github.kuroxbyte:kmapx-runtime:0.1.0")
ksp("io.github.kuroxbyte:kmapx-frontend-ksp:0.1.0")
}
# gradle.properties
ksp.useKSP2=true
Con eso, cada clase anotada genera su mapper al compilar. El código queda en
build/generated/ksp/main/kotlin/, en el mismo paquete que la clase anotada. Para
Kotlin Multiplatform, KSP se declara por target — está explicado paso a paso en la
guía multiplataforma.
Dos modos, un motor¶
kmapx acepta dos estilos de declaración. Ambos producen exactamente el mismo plan de mapeo y el mismo código generado; elige por arquitectura, no por funcionalidad.
Modo embedded (estilo JPA/Jackson): la configuración vive anotada en el modelo. Es el camino más corto — una anotación en la clase y a compilar:
@MapTo(PersonDto::class)
data class Person(val name: String, val age: Int)
val dto = person.toPersonDto() // extension generada
Modo contract (estilo MapStruct/DDD): el mapeo se declara como una interfaz en tu capa de
infraestructura y el dominio queda sin una sola anotación — ideal para arquitectura
hexagonal, con profiles corporativos reutilizables (@MapperConfig), herencia de configuración
(inheritFrom) e inversos declarados (@InverseOf):
@Mapper
interface PersonMapper {
fun toDto(p: Person): PersonDto
}
// genera: object PersonMapperImpl : PersonMapper
Por dónde empezar¶
| Quiero… | Leer |
|---|---|
| Aprenderlo de punta a punta | Guía de referencia |
| Entender los patrones de mapeo (renombres, nulls, rutas, bidireccional) | Guía de patrones |
| Ver funcionalidades combinadas en código que COMPILA | Ejemplos avanzados |
Entender un error KMXnnn |
Catálogo de diagnósticos — generado desde el código |
| Migrar desde MapStruct | Guía de migración |
| Usarlo en KMP | Guía multiplataforma |
| Ver un ejemplo completo de principio a fin | Ejemplo end-to-end |
| Saber qué NO hace (y por qué) | Capacidades y limitaciones |
Cómo se ve un error¶
Todos los diagnósticos salen en una sola pasada de compilación (nunca "arreglas uno y descubres el siguiente"), con formato canónico y acción sugerida:
[KMX002] com.example.PersonDto.age no source property found for constructor parameter 'age'.
Did you mean 'ageYears'? Fix: add a matching source property, or use @MapField(from = "...") to rename.
Lo que lo distingue¶
- Null-safety con cascada:
T? → Tes error salvo salida declarada — por campo, por mapper, por profile o global, con precedencia explícita (el nivel más específico gana). Nunca hay un default silencioso. - Sin strings con código: donde MapStruct usa
expression = "java(...)"o@Named("short"), kmapx usa funciones yKClassreales — el refactor del IDE los sigue, y un converter que no encaja es un error de compilación, no una sorpresa en runtime. - Invertibilidad validada:
@BiMapToy@InverseOfverifican en compile-time que la vuelta reconstruye; toda asimetría es un error con nombre (KMX028). - KMP real: las clases anotadas viven en
commonMainy el processor genera código idéntico por target — la misma suite de tests corre en JVM, Node y nativo. - Documentación que no puede mentir: los ejemplos de esta doc los compila el build, y el catálogo de diagnósticos se genera desde el código fuente.