Object Mapping advanced features & QoL with Kotlin
Tags: opensource, kotlin, architecture, oop
Published: 2022-08-09 18:33:31

When working with multi-layered applications, external libraries, a legacy code base or external APIs, we are often required to map between different objects or data structures.

In this tutorial, we will check out some Object Mapping libraries advanced features to simplify this task while saving development and maintenance time.

In our examples we will use the library [ShapeShift](https://github.com/krud-dev/shapeshift/). It's a light-weight object mapping library for Kotlin/Java with lots of cool features.

# Auto Mapping
We will start with a bang. Auto mapping can and will save you lots of time and boiler-plate code. Some applications require manual mapping between objects, but most applications will save tons of time working on boring boiler-plate by just using this one feature. And it gets even better, with ShapeShift's default transformers we can even use automatic mapping between different data types.

## Simple Mapping
Let's start with a simple example for auto mapping. We have our two objects, imagine they could also have tens, hundreds, or even thousands (for the crazy people here) of fields.

```kotlin
class User {
    var id: String = ""
    var name: String? = null
    var email: String? = null
    var phone: String? = null
}

class UserDTO {
    var id: String = ""
    var name: String? = null
    var email: String? = null
    var phone: String? = null
}
```

We want to map all the field from `User` to `UserDTO`. Using auto mapping we don't need to write any boiler-plate code. The mapper will be defined as follow:

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

Voila! All the fields will be mapped automatically without any manual boiler-plate code.

## Advanced Mapping
In this example we will use the power of [default transformers](https://shapeshift.krud.dev/api-documentation/transformers#default-transformers) to take auto mapping even further.

```kotlin
class User {
    var id: String = ""
    var name: String? = null
    var birthDate: Date? = null
}

class UserDTO {
    var id: String = ""
    var fullName: String? = null
    var birthDate: Long? = null
}
```

Note that the types of the `birthDate` field are different in the source and destination classes. But using the power of default transformers we can still use auto mapping here.

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

We changed the auto mapping strategy to `BY_NAME` so it will map fields also with different types. Now we need to register a default transformer to the `ShapeShift` instance in order for it to know how to transform `Date` to `Long`.

```kotlin
val shapeShift = ShapeShiftBuilder()
    .withTransformer(DateToLongMappingTransformer(), true)
    .build()
```

We can also add manual mapping on top of the auto mapping in order to add/change behavior. The source and destination classes have different names for the `name` field so we will add manual mapping for it.

```kotlin
val mapper = mapper<User, UserDTO> {
    autoMap(AutoMappingStrategy.BY_NAME)
    User::name mappedTo UserDTO::fullName
}
```

Auto mapping is great for use cases that does not require specific mapping. It helps reduce the amount manual boiler-plate code needed to configure mapping and also helps you keep your sanity.

# Transformers
Transformers are very useful feature that allows you to transform the type/value of a field to a different type/value when mapping a field.

Some use cases we have been using widely:
- Transform date to long and vice versa between server and client objects.
- Transform JSON string to it's actual type and vice versa between server and client objects.
- Transform comma separated string to list of enums.
- Transform another object id to its object or one of its fields from the DB using Spring transformers.

## Basic Transformers
We will start with a simple transformer example. Date-to-Long  and Long-to-Date transformers:
```kotlin
class DateToLongMappingTransformer : MappingTransformer<Date, Long> {
    override fun transform(context: MappingTransformerContext<out Date>): Long? {
        return context.originalValue?.time
    }
}

class LongToDateMappingTransformer : MappingTransformer<Long, Date> {
    override fun transform(context: MappingTransformerContext<out Long>): Date? {
        context.originalValue ?: return null
        return Date(context.originalValue)
    }
}
```

All we need to do now is to [register](https://shapeshift.krud.dev/api-documentation/transformers#registering-transformers) them.

```kotlin
val shapeShift = ShapeShiftBuilder()
    .withTransformer(DateToLongMappingTransformer(), true) // "true" is optional, we are registering the transformers as default transformers, more on that later.
    .withTransformer(LongToDateMappingTransformer(), true)
    .build()
```

That's it! We can now use the transformers when mapping objects.

```kotlin
class User {
    var id: String = ""
    var name: String? = null
    var birthDate: Date? = null
}

class UserDTO {
    var id: String = ""
    var name: String? = null
    var birthDate: Long? = null
}

val mapper = mapper<User, UserDTO> {
    User::id mappedTo UserDTO::id
    User::name mappedTo UserDTO::name
    User::birthDate mappedTo UserDTO::birthDate withTransformer DateToLongMappingTransformer::class // We don't have to state the transformer here because it is a default transformer
}
```

## Inline Transformers
In some use cases we want to transform the value but we don't need a reusable transformer and we don't want to create a class just for a one time use.

Inline transformers for the rescue! Inline transformers allow to transform the value without the need to create and register and transformer.

```kotlin
val shapeShift = ShapeShiftBuilder()
        .withMapping<Source, Target> {
            // Map birthDate to birthYear with a transformation function
            Source::birthDate mappedTo Target::birthYear withTransformer { (originalValue) ->
                originalValue?.year
            }
        }
        .build()
```

## Advanced Transformers
Transformers also allow us to do transformations with the DB or other data sources.

In this example we will use the power of the [Spring Boot integration](https://shapeshift.krud.dev/guides/spring-usage) to create transformers with DB access.

We have three models:
* Job - DB entity.
* User - DB entity.
* UserDTO - Client model.

```kotlin
class Job {
    var id: String = ""
    var name: String = ""
}

class User {
    var id: String = ""
    var jobId: String? = null
}

class UserDTO {
    var id: String = ""
    var jobName: String? = null
}
```

We want 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.

We will use a ShapeShift's [Spring integration](https://shapeshift.krud.dev/guides/spring-usage) to create a component as a transformer to access our DAO bean.

```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 
    }
}
```

All that's left to do is to use this transformer in our mapping.

```kotlin
val mapper = mapper<User, UserDTO> {
    User::id mappedTo UserDTO::id
    User::jobId mappedTo UserDTO::jobName withTransformer JobIdToNameTransformer::class
}
```

Another bonus of using transformers is their reusability. In some use cases, We could create more generic transformers that will have application-wide usage.

## Default Transformers
When registering transformers you can indicate wether a transformer is a [default transformer](https://shapeshift.krud.dev/api-documentation/transformers#default-transformers). A default transformer of types \<A, B\> is used when you map a field of type \<A\> to field of type \<B\> without specifying a transformer to be used.

As we already seen, default transformers are useful for recurring transformations and especially for automatic mapping.

# Deep Mapping
What if we want to map from/to fields that available inside a field which is an object? We can even do that, easily.

In order to access child classes we can use the `..` operator. Let's look at the following example.

```kotlin
class From {
    var child: Child = Child()
    
    class Child {
        var value: String?
    }
}

class To {
    var childValue: String?
}
```

We want to map the `value` field in `Child` class inside the `From` class to the `childValue` field in the `To` class. We will create a mapper with the `..` operator.

```kotlin
val mapper = mapper<From, To> {
    From::child..From.Child::value mappedTo To::childValue
}
```

Let's take it one step further with multi level depth.

```kotlin
class From {
    var grandChildValue: String?
}

class To {
    var child: Child = Child()
    
    class Child {
        var grandChild: GrandChild = GrandChild()
    }
    
    class GrandChild {
        var value: String?
    }
}
```

To access the grand child field we just use the `..` operator twice.

```kotlin
val mapper = mapper<From, To> {
    From::grandChildValue mappedTo To::child..To.Child::grandChild..To.GrandChild::value
}
```

# Conditional Mapping
Conditions allow us to add a predicate to a specific field mapping to determine whether this field should be mapped.

Using this feature is as easy as creating a condition.

```kotlin
class NotBlankStringCondition : MappingCondition<String> {
    override fun isValid(context: MappingConditionContext<String>): Boolean {
        return !context.originalValue.isNullOrBlank()
    }
}
```

And adding the condition to the desired field mapping.

```kotlin
data class SimpleEntity(
    val name: String
)

data class SimpleEntityDisplay(
    val name: String = ""
)

val mapper = mapper<SimpleEntity, SimpleEntityDisplay> {
    SimpleEntity::name mappedTo SimpleEntityDisplay::name withCondition NotBlankStringCondition::class
}
```

## Inline Conditions
Like transformers, conditions can also be added inline using a function.

```kotlin
val mapper = mapper<SimpleEntity, SimpleEntityDisplay> {
    SimpleEntity::name mappedTo SimpleEntityDisplay::name withCondition {
        !it.originalValue.isNullOrBlank()
    }
}
```

# Annotations Mapping
This specific feature receives lots of hate because it breaks the separation of concerns principle. Agreed, this could be an issue in some applications, but in some use cases where all objects are part of the same application it can also be very useful to configure the mapping logic on top of the object. Check out the [documentation](https://shapeshift.krud.dev/api-documentation/annotations) and decide for yourself.

# Conclusion
Object mapping libraries are not the solution for every application. For small, simple applications using boiler-plate mapping functions are more than enough. But, when developing larger, more complex applications, object mapping libraries can take your code to the next level, saving you development and maintenance time. All of these while reducing the amount of boiler-plate code and overall improving the development experience.

On a personal note, I used to work with manual mapping functions and was ok with it. It was "just" some simple lines of code. After upgrading our applications to use object mapping as part of our "boiler-plate free" framework (We will discuss that framework at a later time), I can't go back. Now we spend more time on what's important and interesting and almost no time on boring boiler-plate code.