The Kotlin Constructor Mapper (KConMapper / KCM) is a Kotlin Symbol Processing (KSP) plugin that can automatically generate extension functions to map variables of one class to the primary constructor of another class.
Let's say you have a class ExampleEntity
. A common use case is to have various Data Transfer Object (DTO) classes
that we want to convert into such an ExampleEntity
class.
it is common for many of the DTO parameters to be the same as those in the corresponding entity class. As a result, we often find ourselves having to manually map the DTO to the target class, which can be a tedious and redundant task.
Our entity might look like the following:
import java.time.ZonedDateTime
import java.util.*
data class ExampleEntity(
val uid: UUID = UUID.randomUUID(),
val name: String,
val timestamp: ZonedDateTime
)
Now we want to have a DTO for the CRUD operations of update and create.
The create case might look like the following:
import java.time.ZonedDateTime
data class CreateExampleDTO(
val name: String,
val timestamp: ZonedDateTime
)
While the create case lacks the UUID
because it's not created yet, the
update case will contain it:
import java.time.ZonedDateTime
data class UpdateExampleDTO(
val uid: UUID,
val name: String,
val timestamp: ZonedDateTime
)
To create mapping functions, you simply need to add the @KConMapper
annotation above the target class.
- The target class,
ExampleEntity
, is the class whose constructor serves as the schema for the mapping function. - The source classes,
CreateExampleDTO
andUpdateExampleDTO
, are the classes whose parameters we want to map to the constructor schema of the target class.
import java.time.ZonedDateTime
import java.util.*
@KConMapper(fromClasses = [CreateExampleDTO::class, UpdateExampleDTO::class])
data class ExampleEntity(
val uid: UUID = UUID.randomUUID(),
val name: String,
val timestamp: ZonedDateTime
)
By executing the kspKotlin
task, the respective mapping functions get automatically generated as extension functions
of the
source classes.
With these mapping functions, we can implement the following:
// For the create case:
val createExampleDTO = CreateExampleDTO()
val exampleEntity: ExampleEntity = createExampleDTO.toExampleEntity()
// And for the update case:
val updateExampleDTO = UpdateExampleDTO()
val exampleEntity: ExampleEntity = updateExampleDTO.toExampleEntity()
If we want to generate mapping functions from the annotated class to another target class, we can supply the toClasses
argument:
import java.time.ZonedDateTime
import java.util.*
@KConMapper(toClasses = [CreateExampleDTO::class, UpdateExampleDTO::class])
data class ExampleEntity(
val uid: UUID = UUID.randomUUID(),
val name: String,
val timestamp: ZonedDateTime
)
The code snippet from above would generate the following:
val exampleEntity: ExampleEntity = ExampleEntity()
// For the create case:
val createExampleDTO: CreateExampleDTO = exampleEntity.toCreateExampleDTO()
// And for the update case:
val updateExampleDTO: UpdateExampleDTO = exampleEntity.toUpdateExampleDTO()
The KConMapper mapping function works by matching the names of the variables in the source class
to the ones in the target class. If the names of the variables in the source class do not match
the names in the target class, you can use the @KConMapperProperty
annotation to specify which
variables in the source class should be mapped to which variables in the target class.
For example:
@KConMapper(fromClasses = [CreateExampleDTO::class])
data class ExampleEntity(val name: String, val age: Int)
data class CreateExampleDTO(val fullName: String, val yearsOld: Int)
In this example, the fullName variable in the CreateExampleDTO
class does
not have a matching variable in the ExampleEntity
class.
To map the fullName
variable to the name
variable in the ExampleEntity
class,
we would use the @KConMapperProperty
annotation like this:
@KConMapper(fromClasses = [CreateExampleDTO::class])
data class ExampleEntity(val name: String, val age: Int)
data class CreateExampleDTO(
@KConMapperProperty("name") val fullName: String,
@KConMapperProperty("age") val yearsOld: Int
)
With the @KConMapperProperty
annotation in place, the exampleMapper
function will map the fullName variable in the CreateExampleDTO class to the name variable in the ExampleEntity class and the yearsOld variable to the age variable when it is called.
You can adapt the configuration of KConMapper to your needs.
To suppress outputted warnings about missing variable mappings, you can add the following to your build.gradle
file:
ksp {
arg("kconmapper.suppressMappingMismatchWarnings", "true")
}
The following section describes the required setup to use the KConMapper KSP plugin in your project.
Lookup the matching KSP version from the official GitHub release page of the KSP repository. As an example: If you're using Kotlin version
1.9.20
, the latest KSP would be1.9.20-1.0.13
.
groovy - build.gradle(:module-name)
plugins {
// Depends on your project's Kotlin version
id 'com.google.devtools.ksp' version '1.9.20-1.0.13'
}
kotlin - build.gradle.kts(:module-name)
plugins {
// Depends on your project's Kotlin version
id("com.google.devtools.ksp") version "1.9.20-1.0.13"
}
Add the KConMapper plugin dependency:
groovy - build.gradle(:module-name)
repositories {
maven { url = 'https://jitpack.io' }
}
dependencies {
// ..
implementation 'com.github.yanneckreiss.kconmapper:annotations:1.0.0-alpha08'
ksp 'com.github.yanneckreiss.kconmapper:ksp:1.0.0-alpha08'
}
kotlin - build.gradle.kts(:module-name)
repositories {
maven { url = uri("https://jitpack.io") }
}
dependencies {
// ..
implementation("com.github.yanneckreiss.kconmapper:annotations:1.0.0-alpha08")
ksp("com.github.yanneckreiss.kconmapper:ksp:1.0.0-alpha08")
}
To be able to access the generated classes in your project, you need to add them to your
sourceSets
:
Android project
android {
sourceSets.configureEach {
kotlin.srcDir("$buildDir/generated/ksp/$name/kotlin/")
}
}
Kotlin JVM or equal project
kotlin.sourceSets {
getByName(name) {
kotlin.srcDir("$buildDir/generated/ksp/$name/kotlin")
}
}
Copyright 2022 Yanneck Reiß
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.