# Transformers

In this guide we will see how we can use transformers to map fields. Field transformers are a way to transform a field from one type to another when mapping it to a destination class. For example, you might want to map a field from a `String` to a `List<String>` where the source field is comma delimited. In our example, we will explore this use case, as well as an implicit (default) transformation from `Date` to `Long` (milliseconds).

## Classes

Like before, we start by defining our source and destination classes:

{% tabs %}
{% tab title="Kotlin" %}

```kotlin
data class SimpleEntity(
    val creationDate: Date,
    val commaDelimitedString: String, 
)
```

{% endtab %}

{% tab title="Java" %}

```java
public class SimpleEntity {
    private Date creationDate;
    private String commaDelimitedString;

    public Date getCreationDate() {
        return creationDate;
    }

    public void setCreationDate(Date creationDate) {
        this.creationDate = creationDate;
    }

    public String getCommaDelimitedString() {
        return commaDelimitedString;
    }

    public void setCommaDelimitedString(String commaDelimitedString) {
        this.commaDelimitedString = commaDelimitedString;
    }
}
```

{% endtab %}
{% endtabs %}

{% tabs %}
{% tab title="Kotlin" %}

```kotlin
data class SimpleEntityDisplay(
    val creationDate: Long = 0,
    val stringList: List<String> = emptyList()
)
```

{% endtab %}

{% tab title="Java" %}

```java
public class SimpleEntityDisplay {
    private long creationDate = 0;
    private List<String> stringList = new ArrayList<>();

    public long getCreationDate() {
        return creationDate;
    }

    public void setCreationDate(long creationDate) {
        this.creationDate = creationDate;
    }

    public List<String> getStringList() {
        return stringList;
    }

    public void setStringList(List<String> stringList) {
        this.stringList = stringList;
    }
}
```

{% endtab %}
{% endtabs %}

## Creating Transformer

Let's first create our custom `StringToListMappingTransformer`;

{% tabs %}
{% tab title="Kotlin" %}

```kotlin
class StringToListMappingTransformer : MappingTransformer<String, List<String>> {
    override fun transform(context: MappingTransformerContext<out String>): List<String>? {
        return context.originalValue?.split(",")
    }
}
```

{% endtab %}

{% tab title="Java" %}

```java
public class StringToListMappingTransformer implements MappingTransformer<String, List<String>> {
    @Nullable
    @Override
    public List<String> transform(@NonNull MappingTransformerContext<? extends String> context) {
        return context.getOriginalValue() != null
                ? Arrays.asList(context.getOriginalValue().split(","))
                : null;
    }
}
```

{% endtab %}
{% endtabs %}

The `MappingTransformerContext` holds all the required data to perform simple and complex transformations. In this example, all we need to do is to take the original value and split it.

## Registering Transformers

Since we're using custom transformers, we will have to instantiate ShapeShift using `ShapeShiftBuilder` and define our two transformers. In ShapeShift, you define a transformer by providing a `TransformerRegistration` object.

The registration object is used to define the type of the transformer, its instance, and whether it's a default transformer. We will register the `DateToLongTransformer` as a [default transformer](#default-transformers), and the `StringToListTransformer` as a normal transformer.

{% tabs %}
{% tab title="Kotlin" %}

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

{% endtab %}

{% tab title="Java" %}

```java
ShapeShift shapeShift = new ShapeShiftBuilder()
        .withTransformer(new MappingTransformerRegistration(
                Date.class,
                Long.class,
                new DateToLongMappingTransformer(),
                true
        ))
        .withTransformer(new MappingTransformerRegistration(
                String.class,
                List.class,
                new StringToListMappingTransformer(),
                false
        ))
        .build();
```

{% endtab %}
{% endtabs %}

## Default Transformers

When [registering transformers](#registering-transformers) you can indicate wether a transformer is a default transformer. 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.

ShapeShift comes out of the box with some default transformers. The default transformers are available in the `dev.krud.shapeshift.transformer` package [here](https://github.com/krud-dev/shapeshift/tree/master/shapeshift/src/main/kotlin/dev/krud/shapeshift/transformer).&#x20;

Examples for default transformers: `AnyToStringMappingTransformer`, `DateToLongMappingTransformer`.

### Exclude Default Transformers

To exclude the out of the box default transformers just call the `excludeDefaultTransformers` when creating the ShapeShift instance.

{% tabs %}
{% tab title="Kotlin" %}

```kotlin
val shapeShift = ShapeShiftBuilder()
    .excludeDefaultTransformers()
    .build()
```

{% endtab %}

{% tab title="Java" %}

```java
ShapeShift shapeShift = new ShapeShiftBuilder()
        .excludeDefaultTransformers()
        .build();
```

{% endtab %}
{% endtabs %}

## Using Transformers

### Annotations

We can now add our annotations;

{% tabs %}
{% tab title="Kotlin" %}

```kotlin
@DefaultMappingTarget(SimpleEntityDisplay::class)
data class SimpleEntity(
    @MappedField
    val creationDate: Date,

    @MappedField(transformer = StringToListMappingTransformer::class, mapTo = "stringList")
    val commaDelimitedString: String
)
```

{% endtab %}

{% tab title="Java" %}

```java
@DefaultMappingTarget(SimpleEntityDisplay.class)
public class SimpleEntity {
    @MappedField
    private Date creationDate;
    @MappedField(transformer = StringToListMappingTransformer.class, mapTo = "stringList")
    private String commaDelimitedString;
    // Getters and Setters...
}
```

{% endtab %}
{% endtabs %}

Note that we did not need to specify a transformer on `creationDate` since the `DateToLongTransformer` is a default transformer for the `Date` type with a `Long` destination type.

### Kotlin DSL

We can create the same mapping with the DSL using the `withTransformer` function.

```kotlin
val mapper = mapper<SimpleEntity, SimpleEntityDisplay> {
    SimpleEntity::creationDate mappedTo SimpleEntityDisplay::creationDate
    SimpleEntity::commaDelimitedString mappedTo SimpleEntityDisplay::stringList withTransformer StringToListMappingTransformer::class
}
```

The DSL also supports inline transformer. When we don't need to reuse a transformer we can just add its logic to the DSL.

```kotlin
val mapper = mapper<SimpleEntity, SimpleEntityDisplay> {
    SimpleEntity::creationDate mappedTo SimpleEntityDisplay::creationDate
    SimpleEntity::commaDelimitedString mappedTo SimpleEntityDisplay::stringList withTransformer {
        it.originalValue?.split(",")
    }
}
```

### Java Builder

We can create the same mapping with the builder using the `withTransformer` function.

```java
MappingDefinition mappingDefinition = new MappingDefinitionBuilder(SimpleEntity.class, SimpleEntityDisplay.class)
        .mapField("creationDate", "creationDate")
        .mapField("commaDelimitedString", "stringList")
        .withTransformer(StringToListMappingTransformer.class)
        .build();
```

As the DSL, the builder also supports inline transformer.

```java
MappingDefinition mappingDefinition = new MappingDefinitionBuilder(SimpleEntity.class, SimpleEntityDisplay.class)
        .mapField("creationDate", "creationDate")
        .mapField("commaDelimitedString", "stringList")
        .withTransformer(context -> context.getOriginalValue() != null
                ? Arrays.asList(((String) context.getOriginalValue()).split(","))
                : null)
        .build();
```

## Test

Let's write a test to verify that our mapping is correct;

{% tabs %}
{% tab title="Kotlin" %}

```kotlin
@Test
fun `test mapping for SimpleEntityDisplay`() {
    val shapeShift = ShapeShiftBuilder()
        .withTransformer(DateToLongTransformer(), default = true)
        .withTransformer(StringToCommaSeparatedStringListTransformer())
        .build()
    val simpleEntity = SimpleEntity(
            Date(),
            "one,two,three"
    )
    val result = shapeShift.map<SimpleEntityDisplay>(simpleEntity)
    expectThat(result.creationDate)
            .isEqualTo(simpleEntity.creationDate.time)
    expectThat(result.stringList)
            .isEqualTo(listOf("one", "two", "three"))
}
```

{% endtab %}

{% tab title="Java" %}

```java
@Test
public void testMappingForSimpleEntityDisplay() {
    ShapeShift shapeShift = new ShapeShiftBuilder()
            .excludeDefaultTransformers()
            .withTransformer(new MappingTransformerRegistration(
                    Date.class,
                    Long.class,
                    new DateToLongMappingTransformer(),
                    true
            ))
            .withTransformer(new MappingTransformerRegistration(
                    String.class,
                    List.class,
                    new StringToListMappingTransformer(),
                    false
            ))
            .build();

    SimpleEntity simpleEntity = new SimpleEntity();
    simpleEntity.setCreationDate(new Date());
    simpleEntity.setCommaDelimitedString("one,two,three");

    SimpleEntityDisplay result = shapeShift.map(simpleEntity, SimpleEntityDisplay.class);

    assertEquals(result.getCreationDate(), simpleEntity.getCreationDate().getTime());
    assertEquals(result.getStringList(), Arrays.asList("one", "two", "three"));
}
```

{% endtab %}
{% endtabs %}

## Full Example

You can check out the full example [here](https://github.com/krud-dev/shapeshift/tree/master/example/kotlin/transformer-mapping).
