Kotlin Object Mapping - Pros & Cons
Tags: kotlin, java, spring
Published: 2022-06-01 13:36:00

# What is Object Mapping?
Multi-layered applications often have similar but different object models, where the data in two models may be similar but the structure and concerns of the models are different. 
Writing the mapping code is a tedious and error-prone task. Object mapping makes it easy to convert one model to another.

# Simple Example
Let's say we have a REST API application with two models. One for the server and one client facing:

User (Server):
```kotlin
data class User(
    val id: String,
    val name: String,
    val email: String,
    // isBlocked is server side only
    val isBlocked: Boolean
)
```

UserDTO (Client):
```kotlin
data class UserDTO(
    val id: String,
    val name: String,
    val email: String
)
```
## Conversion
We have a couple of options:

### Extension Methods
We can use simple extension methods.
```kotlin
fun User.toUserDTO(): UserDTO {
    return UserDTO(id, name, email)
}
```
Simple and easy, but requires additional boiler-plate code we need to maintain every time we change the model.

### Object Mapping
We will use the open source object mapping library [ShapeShift](https://github.com/krud-dev/shapeshift) for the examples.

#### Annotations
The first step is to add the annotations to the fields we want to map.
```kotlin
@DefaultMappingTarget(UserDTO::class)
data class User(
    @MappedField
    var id: String = "",
    @MappedField
    var name: String = "",
    @MappedField
    var email: String = "",
    // isBlocked is server side only and not mapped to client DTO
    var isBlocked: Boolean = false
)
```
All that's left is to convert.
```kotlin
val shapeShift = ShapeShiftBuilder().build()
val user = User("xyz", "john doe", "john@email.com", false)
val userDTO = shapeShift.map<UserDTO>(user)
```
#### DSL
In some cases we can't change the data classes code (or don't want to). For these use cases we create a standalone mapper between the two classes using Kotlin DSL.
```kotlin
val mapper = mapper<User, UserDTO> {
    User::id mappedTo UserDTO::id
    User::name mappedTo UserDTO::name
    User::email mappedTo UserDTO::email
}
```
And now the conversion. Note that we are registering the mapper to the ShapeShift instance.
```kotlin
val shapeShift = ShapeShiftBuilder().withMapping(mapper).build()
val user = User("xyz", "john doe", "john@email.com", false)
val userDTO = shapeShift.map<UserDTO>(user)
```
Seems like a lot of work for a simple conversion, right? Maybe... Let's continue to the next example.

# Auto Mapping
What if I told you there is a way to remove almost if not all the mapping boiler-plate code? In many use cases we have similar classes we need to map between. This is where automatic mapping comes to the rescue.

Let's continue with the same classes.

```kotlin
data class User(
    val id: String,
    val name: String,
    val email: String,
    val isBlocked: Boolean
)

data class UserDTO(
    val id: String,
    val name: String,
    val email: String
)
```

Using automatic mapping our mapper will be the following.

```kotlin
val mapper = mapper<User, UserDTO> {
    autoMap(AutoMappingStrategy.BY_NAME_AND_TYPE)
}
```

Voila! No boiler-plate code to configure fields manually. [Auto mapping](https://shapeshift.krud.dev/api-documentation/auto-mapping) also comes with advanced features for more complex use cases.

# Generic Example
When our projects start to scale, we want to add generic functionality to improve productivity and decrease maintenance. For example:
* Generic CRUD controller for entities.
* Generic export to excel/pdf/...

In order for us to be able to implement these generic functionality we need to be able to convert our objects back and forth between classes.

We can use the [factory method pattern](https://en.wikipedia.org/wiki/Factory_method_pattern) to do it, simple and straightforward, but agin requires more boiler-plate code we need to maintain when we add new models.

Another option is (surprise surprise) to use object mapping.
```kotlin
inline fun <reified ExportModel : Any> export(model: Any) {
    val shapeShift = ShapeShiftBuilder().build()
    val exportModel = shapeShift.map<ExportModel>(model)
    // export logic...
}

val user = User(/*...*/)
export<UserExport>(user)
```
That's it. Zero boiler-plate code and we got ourselves the generic functionality we know and love.

## Type Safety
Let's take it further and add type safety to the export method. For that we will add two interfaces:
```kotlin
interface ExportModel {
}

interface BaseModel<EM: ExportModel> {
}
```

Now we will update our classes to implement the interfaces.
```kotlin
class UserExport: ExportModel {
    // ...
}

class User: BaseModel<UserExport> {
    // ...
}
```

And change our generic method respectively.
```kotlin
inline fun <reified EM : ExportModel> export(model: BaseModel<EM>) {
    val shapeShift = ShapeShiftBuilder().build()
    val exportModel = shapeShift.map<EM>(model)
    // export logic...
}
```

That's it. We got ourselves type safe generic export method.
```kotlin
val user = User(/*...*/)
export(user)
```

# Spring Example
Taking it up a notch to the next level. We have three models.
* Job - DB entity.
* User - DB entity.
* UserDTO - Client model.

```kotlin
@Entity
@Table(name = "jobs")
class Job {
    var id: String = ""
    var name: String = ""
}

@Entity
@Table(name = "users")
class User {
    var id: String = ""
    var jobId: String? = null
}

class UserDTO {
    var id: String = ""
    var jobName: String? = null
}
```
We need to convert the `jobId` on User to `jobName` on UserDTO by querying the job from the DB and setting it on the DTO.

In Spring's case, you generally avoid interaction with the application context from static functions or functions on domain objects and because of that they cannot be invoked from extensions.

Object mapping to the rescue (surprise!). Let's create a custom transformer to do the heavy lifting for us.

```kotlin
@Component
class JobIdToNameTransformer(
    private val jobDao: JobDao
) : MappingTransformer<String, String>() {
    override fun transform(context: MappingTransformerContext<out String>): String? {
        context.originalValue ?: return null
        val job = jobDao.findJobById(context.originalValue!!)
        return job.name 
    }
}
```

The complicated part is over. Now we just need to add the annotations to the User model.

```kotlin
@Entity
@Table(name = "users")
@DefaultMappingTarget(UserDTO::class)
class User {
    @MappedField
    var id: String = ""
    @MappedField(transformer = JobIdToNameTransformer::class, mapTo = "jobName")
    var jobId: String? = null
}
```

That's all. The great part is that we can use the same transformer every time we need to convert a job id to job name on any model.

```kotlin
val shapeShift = ShapeShiftBuilder().build()
val user = User(/*...*/)
val userDTO = shapeShift.map<UserDTO>(user)
```

A full spring example code is available [here](https://github.com/krud-dev/shapeshift/tree/master/example/kotlin/spring-mapping).

# Pros & Cons
Each conversion method has its own pros & cons.

## Extension Methods
### Pros
* Simple implementation.
* Clear code - no annotations "magic".

### Cons
* Lots of boiler plate code.
* Limited functionality.
* Does not scale well.

## Object Mapper
### Pros
* Little to none boiler-plate code.
* Auto mapping.
* Generic code.
* [Advanced features](https://dev.to/krud/object-mapping-advanced-features-qol-with-kotlin-5dgn).
* Code reuse.

### Cons
* Requires to learn new library.
* Annotations "magic".

# Summary
Like most things in development, there is no right answer here. The answer is that it depends on the project. For small simple projects extension methods are more than enough to convert classes, but for large enterprise projects using an object mapper can give the freedom to achieve better architecture and more generic code that can keep moving forward with large codebase and lots of models.