Skip to content

The Kotlin Constructor Mapper (KConMapper / KCM) automatically generates mapping functions for performing mapping between one class and the constructor of another class via the Kotlin Symbol Processing (KSP) compiler plugin.

License

Notifications You must be signed in to change notification settings

YanneckReiss/KConMapper

Repository files navigation

Version License Apache 2.0 kotlin

KConMapper (KCM)

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.


How To Use The Plugin

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.

Here comes the KConMapper into play.

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 and UpdateExampleDTO, 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()

Property name overriding

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.


Configurations

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")
}

Setup

The following section describes the required setup to use the KConMapper KSP plugin in your project.

1. Add the KSP (Kotlin Symbol Processing) plugin:

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 be 1.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"
}

2. Dependency

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")
}

3. Add source sets

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")
    }
}

License

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.

About

The Kotlin Constructor Mapper (KConMapper / KCM) automatically generates mapping functions for performing mapping between one class and the constructor of another class via the Kotlin Symbol Processing (KSP) compiler plugin.

Topics

Resources

License

Stars

Watchers

Forks

Languages