Advanced Mapping

Nested fields and multiple targets annotations mapping.

In this guide, we'll review mapping nested fields, as well as defining mappings to multiple targets.

Classes

We start by defining our source and destination classes, and additionally a child class which will be used by both.

Starting with the child class:

data class AdvancedChildEntity(
    val childName: String
)

Followed by our source class:

data class AdvancedEntity(
    val name: String,
    val firstChild: AdvancedChildEntity,
    val secondChild: AdvancedChildEntity
)

In this example, we will have two separate destination classes, AdvancedEntityDisplay and ReducedAdvancedEntityDisplay.

data class AdvancedEntityDisplay(
    val name: String = "",
    val firstChildName: String = "",
    val secondChildName: String = ""
)
data class ReducedAdvancedEntityDisplay(
    val name: String = "",
    val firstChildName: String = ""
)

Annotations

Let's start adding annotations to the source class. We will define a @DefaultMappingTarget annotation on the AdvancedEntity class, which will indicate that all fields annotated with @MappedField that do not specify a target should be mapped to the AdvancedEntityDisplay class. Seeing as we have two destinations in this example, we will have to define some of the mapping targets manually.

We'll start by defining a simple @MappedField on the name field for both targets. We will also define a @MappedField annotation on the firstChild field, which will indicate that it should be mapped to the firstChildName field on the AdvancedEntityDisplay class, and repeat the same annotation for secondChild. To achieve this we will use the mapFrom and mapTo parameters. Notice that by doing this, we have extracted the name field from the AdvancedEntity class, and mapped it directly to the *ChildName field on the AdvancedEntityDisplay class. The ReducedAdvancedEntityDisplay class will have the same mapping as the AdvancedEntityDisplay class, but will omit the secondChildName field;

@DefaultMappingTarget(AdvancedEntityDisplay::class)
data class AdvancedEntity(
    @MappedField
    @MappedField(target = ReducedAdvancedEntityDisplay::class)
    val name: String,

    @MappedField(mapFrom = "childName", mapTo = "firstChildName")
    @MappedField(target = ReducedAdvancedEntityDisplay::class, mapFrom = "childName", mapTo = "firstChildName")
    val firstChild: AdvancedChildEntity,

    @MappedField(mapFrom = "childName", mapTo = "secondChildName")
    val secondChild: AdvancedChildEntity
)

Test

Finally, let's write two tests to verify that our mapping is working correctly for both targets

@Test
fun `test advanced mapping for AdvancedEntityDisplay`() {
    val shapeShift = ShapeShift()
    val advancedEntity = AdvancedEntity(
            "test",
            AdvancedChildEntity("first child"),
            AdvancedChildEntity("second child")
    )
    val result = shapeShift.map<AdvancedEntityDisplay>(advancedEntity)
    expectThat(result.name)
            .isEqualTo("test")
    expectThat(result.firstChildName)
            .isEqualTo("first child")
    expectThat(result.secondChildName)
            .isEqualTo("second child")
}

@Test
fun `test advanced mapping for ReducedAdvancedEntityDisplay`() {
    val shapeShift = ShapeShift()
    val advancedEntity = AdvancedEntity(
            "test",
            AdvancedChildEntity("first child"),
            AdvancedChildEntity("second child")
    )
    val result = shapeShift.map<ReducedAdvancedEntityDisplay>(advancedEntity)
    expectThat(result.name)
            .isEqualTo("test")
    expectThat(result.firstChildName)
            .isEqualTo("first child")
}

Full Example

You can check out the full example here.

Last updated