Not all classes one wants to map are equivalent. Mappie supports defining explicit mappings for those which cannot be resolved automatically. This can be done via properties, values, or expressions as described in the coming sections.
Suppose we have a data class Person
, and we have the data class PersonDto
which has the property description
which is not defined in Person
data class Person(
val name: String,
val age: Int,
)
data class PersonDto(
val name: String,
val age: Int,
val description: String,
)
If one would define a mapper without an explicit mapping for description
, Mappie will give a compile-time error
stating that the target description
has no source defined. The target property can be assigned in different ways:
- mapping via a source property;
- mapping via a value; or
- mapping via an expression.
Mapping via a Source Property #
Targets can be set via the operator fromProperty
. This will set the target to the given source property.
For example, the following snippet will construct a mapper where PersonDto.description
is set to Person.name
object PersonMapper : ObjectMappie<Person, PersonDto>() {
override fun map(from: Person): PersonDto = mapping {
PersonDto::description fromProperty from::name
}
}
The target type is not always assignable from the source type. There are several ways to handle this. One way is to define a mapper from the source type to the target type. This can be applied explicitly using The Via Operator, or be implicitly applied by Mappie.
It is also possible to transform the property. For example to tweak the value, handle nullability, or transform the source in some other way. See The Transform Operator for some guidelines.
Nullability #
When mapping from a nullable type to a non-nullable type, one has several options. The most flexible option is to use the transform operator.
When the transformation logic is applying a simple non-null assertion operator, or a
requireNotNull
function call, to::x fromPropertyNotNull from::y
steps in as an equivalent alternative to
to::x fromProperty from::y transform { it!! }
Mapping via a Value #
Targets can be set via the operator fromValue
. This will set the target to the given value.
For example, the following snippet will construct a mapper where PersonDto.description
is set to "unknown"
object PersonMapper : ObjectMappie<Person, PersonDto>() {
override fun map(from: Person): PersonDto = mapping {
PersonDto::description fromValue "unknown"
}
}
Mapping via an Expression #
Targets can be set via the operator fromExpression
. This will set the target to the given lambda result.
The difference between fromExpression
and fromValue
is that fromExpression
will take a lambda
function as a parameter, which takes the original source
as a parameter. Allowing for more flexibility.
For example, the following snippet will construct a mapper where PersonDto.description
is set to
"Description: ${from.name}"
.
object PersonMapper : ObjectMappie<Person, PersonDto>() {
override fun map(from: Person): PersonDto = mapping {
PersonDto::description fromExpression { from ->
"Description: ${from.name}"
}
}
}
All mappings can be defined using fromExpression
, but to keep the mappings clean and give Mappie the most information
to suggest improvements to your code, fromProperty
combined with either via
or transform
is preferred.
Handling non-referenceable Targets #
We can use the to
function to refer to constructor parameters which do not have a property or to refer to a setter
method.
For example, suppose that we use the same example as above, but PersonDto.description
does not declare a backing property.
data class PersonDto(
val name: String,
val age: Int,
description: String,
)
We cannot reference description
via a property reference Person::description
. To target the constructor parameter,
we can use to("description")
to reference the constructor parameter
object PersonMapper : ObjectMappie<Person, PersonDto>() {
override fun map(from: Person): PersonDto = mapping {
to("description") fromValue "a constant"
}
}
The to Alias #
We can access the target properties via the target type of the mapper. This can clutter the mapping definition when
many explicit mappings are defined. Mappie defines a special to
property which can be used instead of the target type.
For example, we can use to
refer to the property streetname
of PersonDto
object PersonMapper : ObjectMappie<Person, PersonDto>() {
override fun map(from: Person): PersonDto = mapping {
to::streetname fromProperty from.address::street
}
}
where to::streetname
is equivalent to PersonDto::streetname
.