Transformers
Transform field type/value between the source and target classes.
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).Like before, we start by defining our source and destination classes:
Kotlin
Java
data class SimpleEntity(
val creationDate: Date,
val commaDelimitedString: String,
)
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;
}
}
Kotlin
Java
data class SimpleEntityDisplay(
val creationDate: Long = 0,
val stringList: List<String> = emptyList()
)
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;
}
}
Let's first create our custom
StringToListMappingTransformer
;Kotlin
Java
class StringToListMappingTransformer : MappingTransformer<String, List<String>> {
override fun transform(context: MappingTransformerContext<out String>): List<String>? {
return context.originalValue?.split(",")
}
}
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;
}
}
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.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, and the StringToListTransformer
as a normal transformer.Kotlin
Java
val shapeShift = ShapeShiftBuilder()
.withTransformer(DateToLongMappingTransformer(), default = true)
.withTransformer(StringToListMappingTransformer())
.build()
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();
When 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. Examples for default transformers:
AnyToStringMappingTransformer
, DateToLongMappingTransformer
.To exclude the out of the box default transformers just call the
excludeDefaultTransformers
when creating the ShapeShift instance.Kotlin
Java
val shapeShift = ShapeShiftBuilder()
.excludeDefaultTransformers()
.build()
ShapeShift shapeShift = new ShapeShiftBuilder()
.excludeDefaultTransformers()
.build();
We can now add our annotations;
Kotlin
Java
@DefaultMappingTarget(SimpleEntityDisplay::class)
data class SimpleEntity(
@MappedField
val creationDate: Date,
@MappedField(transformer = StringToListMappingTransformer::class, mapTo = "stringList")
val commaDelimitedString: String
)
@DefaultMappingTarget(SimpleEntityDisplay.class)
public class SimpleEntity {
@MappedField
private Date creationDate;
@MappedField(transformer = StringToListMappingTransformer.class, mapTo = "stringList")
private String commaDelimitedString;
// Getters and Setters...
}
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.We can create the same mapping with the DSL using the
withTransformer
function.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.
val mapper = mapper<SimpleEntity, SimpleEntityDisplay> {
SimpleEntity::creationDate mappedTo SimpleEntityDisplay::creationDate
SimpleEntity::commaDelimitedString mappedTo SimpleEntityDisplay::stringList withTransformer {
it.originalValue?.split(",")
}
}
We can create the same mapping with the builder using the
withTransformer
function.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.
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();
Let's write a test to verify that our mapping is correct;
Kotlin
Java
@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"))
}
@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"));
}
Last modified 1yr ago