Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
<dependency>
<groupId>dev.krud</groupId>
<artifactId>shapeshift</artifactId>
<version>0.8.0</version>
</dependency>implementation 'dev.krud:shapeshift:0.8.0'implementation("dev.krud:shapeshift:0.8.0")ShapeShift – A lightweight, modular, performant and extensible object mapping library for Kotlin/Java.
ShapeShift is a Kotlin first object mapping library. We have built ShapeShift because we wanted a simple to use, minimal boiler plate mapping engine, that is also flexible and supports the most advanced use cases.
Built with Kotlin in mind, ShapeShift was designed around its ecosystem and best practices. The library APIs for mapping with Kotlin:
Annotations - Fully featured annotation based mapping, just add annotations to your objects and ShapeShift handles the rest. Including using custom field transformers, conditional mapping, advanced object decoration and much more.
- Allowing you to define the relations between objects. This allows you to map objects you can't change (or don't want to), like objects from 3rd party libraries. Additionally you can define inline transformations, conditions and decorations, enabling deep customization and very advanced mapping.
ShapeShift also has full Java support available with multiple APIs for mapping:
- Same API as Kotlin with full Java support.
- The equivalent Java API for the Kotlin DSL. Allowing you to define the relations between objects without modifying their code, and map objects you can't change (or don't want to).
ShapeShift main features:
Auto mapping
Custom field transformers
Default transformers
Deep mapping
Check out the quick start guide to learn how get started:
Mapping to destination classes without a no arg constructor.
Due to the fact that ShapeShift uses reflection behind the scenes, destination classes need a no arg constructor. But in some cases you have no control over the destination classes and cannot modify them to add a no arg constructor. This is where Instance Mapping comes into play, you can pass already-instantiated destination objects to the map method.
@DefaultMappingTarget(SimpleEntityDisplay::class)
data class SimpleEntity(
@MappedField
val name: String,
@MappedField
val description: String,
val privateData: String
@DefaultMappingTarget(SimpleEntityDisplay.class)
public class SimpleEntity {
@MappedField
private String name;
@MappedField
private String description;
private
SimpleEntityDisplay does not have a no arg constructor. You can either add a no arg constructor or initiate a new instance of SimpleEntityDisplay and pass it to the map function.
Mapping to destination classes without a no arg constructor.
Due to the fact that ShapeShift uses reflection behind the scenes, destination classes need a no arg constructor. But in some cases you have no control over the destination classes and cannot modify them to add a no arg constructor. This is where Object Suppliers comes into play, you can register object suppliers to the ShapeShift instance to add your own logic for instance generation.
We have two classes, the source class SimpleEntity and the destination class SimpleEntityDisplay
data class SimpleEntityDisplay(
val name: String,
val description: String
)public class SimpleEntityDisplay {
private String name;
private String description;
public SimpleEntityDisplay(String name, String description) {
this.name = name;
this.description = description;
}
// Getters and Setters...
}val shapeShift = ShapeShiftBuilder().build()
val simpleEntity = SimpleEntity("test", "test description", "private data")
val simpleEntityDisplay = SimpleEntityDisplay("", "")
// Passing simpleEntityDisplay as a destination instance
val result = shapeShift.map(simpleEntity, simpleEntityDisplay)ShapeShift shapeShift = new ShapeShiftBuilder().build();
SimpleEntity simpleEntity = new SimpleEntity();
simpleEntity.setName("test");
simpleEntity.setDescription("test description");
simpleEntity.setPrivateData("private data");
SimpleEntityDisplay simpleEntityDisplay = new SimpleEntityDisplay("", "");
// Passing simpleEntityDisplay as a destination instance
SimpleEntityDisplay result = shapeShift.map(simpleEntity, simpleEntityDisplay);All of ShapeShift's features are available in Android. Continue to the Quick Start guide or jump directly to the Annotations, Kotlin DSL or Java Builder APIs.
implementation 'dev.krud:shapeshift:0.8.0'// Add Maven Central to your repositories if needed
repositories {
mavenCentral()
}MAP_NOT_NULL - A strategy that maps only the values of a field that are not null. If the field value is null it will not override the current value in the target class instance. This is the default strategy when creating a new ShapeShift instance.
There are two options for setting the mapping strategy:
Settings the default mapping strategy for the ShapeShift instance.
val shapeShift = ShapeShiftBuilder()
.withDefaultMappingStrategy(MappingStrategy.MAP_ALL)
.build()ShapeShift shapeShift = new ShapeShiftBuilder()
.withDefaultMappingStrategy(MappingStrategy.MAP_ALL)
.build();Overriding the mapping strategy for a specific field.
@MappedField(overrideMappingStrategy = MappingStrategy.MAP_ALL)@MappedField(overrideMappingStrategy = MappingStrategy.MAP_ALL)val mapper = mapper<From, To> {
From::value mappedTo To::value overrideStrategy MappingStrategy.MAP_NOT_NULL
}MappingDefinition mappingDefinition = new MappingDefinitionBuilder(From.class, To.class)
.mapField("value", "value").withMappingStrategy(MappingStrategy.MAP_NOT_NULL)
.build();Multiple mapping targets
Conditional mapping
Mapping decorators
Seamless spring integration
Native Android support

@DefaultMappingTarget(SimpleEntityDisplay::class)
data class SimpleEntity(
@MappedField
val name: String,
@MappedField
val description: String,
val privateData: String
@DefaultMappingTarget(SimpleEntityDisplay.class)
public class SimpleEntity {
@MappedField
private String name;
@MappedField
private String description;
private
The destination class does not have a no arg constructor.
data class SimpleEntityDisplay(
val name: String,
val description: String
)public class SimpleEntityDisplay {
private String name;
private String description;
public SimpleEntityDisplay(String name, String description) {
this.
To solve this issue we need to either use instance mapping or add an object supplier for the class.
Adding object suppliers is available through the ShapeShiftBuilder class. Object suppliers can be added inline or as a separate class.
To create an object supplier class implement the Supplier interface.
And register it to the ShapeShift instance.
It is also possible to add the object supplier logic inline.
Now that we added an object supplier for the SimpleEntityDisplay class we can map to it as if it has a no arg constructor.
Using ShapeShift with a Spring Boot project.
In this guide we will see how we can use ShapeShift's Spring Boot starter to seamlessly add transformers and decorators within Spring projects.
The Spring Boot starter automatically registers mapping transformer beans and decorator beans, as well as a customizer for cases where further customization of the ShapeShift instance is required.
Install the spring library via Maven or Gradle:
In order to register a new transformer, simply create the class and register it as a Spring Bean via your method of choice. (JavaConfig, XML)
Default transformers cannot be registered in this way, see below on how to achieve this
You can register decorators in the same way that you register transformers, simply create the class and register it as a Spring Bean via your method of choice. (JavaConfig, XML)
If you need to be able to affect the ShapeShift instance in ways that are not mentioned above, you can implement a ShapeShiftBuilderCustomizer, customizers are configuration classes that give you access to the ShapeShiftBuilder for the main Spring instance of ShapeShift. To use, simply implement the interface and annotate with @Configuration. In customize, you can do anything you're normally able to do with the standard ShapeShiftBuilder
Conditional mapping of fields by predicates.
Conditions are used to determine wether a field should be mapped according to certain logic. In some use cases it is required to map a field from the source class only if some predicate is true, condition is that predicate.
We start with our two classes, our source class SimpleEntity and our destination class SimpleEntityDisplay.
Decorate mapping operations with additional logic.
In some use cases mapping fields is not enough. Sometimes we need to add additional logic to the mapping. For these use cases we have the decorators, the decorators allow us to perform operations on our models after the mapping has finished.
We start with our two classes, our source class User and our destination class UserDisplay.
In this example, we want to merge the firstName
class SimpleEntityDisplaySupplier : Supplier<SimpleEntityDisplay> {
override fun get(): SimpleEntityDisplay {
return SimpleEntityDisplay("", "")
}
}public class SimpleEntityDisplaySupplier implements Supplier<SimpleEntityDisplay> {
@Override
public SimpleEntityDisplay get() {
return new SimpleEntityDisplay("", "");
}
}val shapeShift = ShapeShiftBuilder()
.withObjectSupplier(SimpleEntityDisplaySupplier())
.build()ShapeShift shapeShift = new ShapeShiftBuilder()
.withObjectSupplier(new SimpleEntityDisplaySupplier(), SimpleEntityDisplay.class)
.build();val shapeShift = ShapeShiftBuilder()
.withObjectSupplier { SimpleEntityDisplay("", "") }
.build()ShapeShift shapeShift = new ShapeShiftBuilder()
.withObjectSupplier(() -> new SimpleEntityDisplay("", ""), SimpleEntityDisplay.class)
.build();val simpleEntity = SimpleEntity("test", "test description", "private data")
val simpleEntityDisplay = SimpleEntityDisplay("", "")
val result = shapeShift.map<SimpleEntityDisplay>(simpleEntity)SimpleEntity simpleEntity = new SimpleEntity();
simpleEntity.setName("test");
simpleEntity.setDescription("test description");
simpleEntity.setPrivateData("private data");
SimpleEntityDisplay result = shapeShift.map(simpleEntity, SimpleEntityDisplay.class);namedescriptionSimpleEntityDisplayprivateDataTo instantiate ShapeShift we use the ShapeShiftBuilder and register our mapper:
All that's left is to map the SimpleEntity instance to the SimpleEntityDisplay class.
In the example above we did basic mapping between fields. But what if we want to map from/to deep fields of child classes?
In order to access child classes we can use the .. operator. Let's look at the following example:
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.
The .. operator is supported in both source and destination fields, it also supports multi level depth.
To access the grand child field we just use the .. operator twice.
Field transformers are a way to transform a field from one type to another when mapping it to a destination class. More about the ins-and-outs of transformers is available here:
The withTransformer function has 2 options to use transformers. Let's look at the following classes.
We want to map the commaDelimitedString field to the stringList field and change the field type from String to List<String> while doing so. To accomplish that we will use a transformer.
Our first option is to create a transformer class, StringToListMappingTransformer;
All we need to do to use our transformer is to pass it to the withTransformer function.
Our second option is to use an inline transformer. When we don't need to reuse a transformer we can just add its logic to the DSL.
Auto mapping is used to reduce the amount of boiler-place code required to configure mapping between two classes. More info about auto mapping is available here:
Auto mapping can be added using the autoMap function.
autoMap function receives the desired auto mapping strategy. It is possible to add any manual mapping to add/change mapping behavior.
Conditions are used to determine wether a field should be mapped according to certain logic. More info about conditions is available here:
Let's look at the following classes.
We want to map the name field only if it's not null or blank. The withCondition function has 2 options to add conditions.
Our first option is to create a condition class. The condition receives context with the original value of the field and checks that it is not null or blank.
We will create our mapper and add the condition.
Our second option is to use an inline condition. When we don't need to reuse a condition we can just add its logic to the DSL.
Decorators allow to add additional logic to the mapping operation. More info about conditions is available here:
Let's look at the following classes.
We want to merge the firstName and lastName fields to the fullName field in addition to mapping them to their respectable fields.
Decorators can be added inline or as a separate class.
To create a decorator class implement the MappingDecorator interface.
And register it to the ShapeShift instance.
It is also possible to add the decorator logic inline.
Due to the fact that ShapeShift uses reflection behind the scenes, destination classes need a no arg constructor. But in some cases you have no control over the destination classes and cannot modify them to add a no arg constructor. This is where Object Suppliers comes into play, you can register object suppliers to the ShapeShift instance to add your own logic for instance generation.
More info about Object Suppliers is available here:
The overrideStrategy function allows you to override the default mapping strategy configured on the ShapeShift instance.
More info about mapping strategy is available here:
<dependency>
<groupId>dev.krud</groupId>
<artifactId>spring-boot-starter-shapeshift</artifactId>
<version>0.8.0</version>
</dependency>implementation 'dev.krud:spring-boot-starter-shapeshift:0.8.0'implementation("dev.krud:spring-boot-starter-shapeshift:0.8.0")@Component
class BeanTransformer : MappingTransformer<String, String> {
override fun transform(context: MappingTransformerContext<out String>): String? {
return context.originalValue?.uppercase()
}
}@Component
public class BeanTransformer implements MappingTransformer<String, String> {
@Nullable
@Override
public String transform(@NonNull MappingTransformerContext<? extends String> context) {
return context.getOriginalValue() != null ? context.getOriginalValue().toUpperCase() : null;
}
}@Configuration
class MyConfiguration {
@Bean
fun beanTransformer(): BeanTransformer {
return BeanTransformer()
}
}@Configuration
public class MyConfiguration {
@Bean
public BeanTransformer beanTransformer() {
return new BeanTransformer();
}
}@Component
class BeanDecorator: MappingDecorator<MyPojo, MyPojoDisplay> {
override fun decorate(context: MappingDecoratorContext<MyPojo, MyPojoDisplay>) {
// decorate
}
}@Component
public class BeanDecorator implements MappingDecorator<MyPojo, MyPojoDisplay> {
@Override
public void decorate(@NonNull MappingDecoratorContext<MyPojo, MyPojoDisplay> context) {
// decorate
}
}@Configuration
class MyConfiguration {
@Bean
fun beanDecorator(): BeanDecorator {
return BeanDecorator()
}
}@Configuration
public class MyConfiguration {
@Bean
public BeanDecorator beanDecorator() {
return new BeanDecorator();
}
}@Configuration
class MyCustomizer : ShapeShiftBuilderCustomizer {
override fun customize(builder: ShapeShiftBuilder) {
builder.withDefaultMappingStrategy(MappingStrategy.MAP_ALL)
}
}@Configuration
public class MyCustomizer implements ShapeShiftBuilderCustomizer {
@Override
public void customize(ShapeShiftBuilder builder) {
builder.withDefaultMappingStrategy(MappingStrategy.MAP_ALL);
}
}data class SimpleEntity(
val name: String,
val description: String,
val privateData: String
)data class SimpleEntityDisplay(
val name: String = "",
val description: String = ""
)val mapper = mapper<SimpleEntity, SimpleEntityDisplay> {
SimpleEntity::name mappedTo SimpleEntityDisplay::name
SimpleEntity::description mappedTo SimpleEntityDisplay::description
}val shapeShift = ShapeShiftBuilder()
.withMapping(mapper)
.build()val simpleEntity = SimpleEntity("test", "test description", "private data")
val simpleEntityDisplay = shapeShift.map<SimpleEntityDisplay>(simpleEntity)class From {
var child: Child = Child()
class Child {
var value: String?
}
}
class To {
var childValue: String?
}val mapper = mapper<From, To> {
From::child..From.Child::value mappedTo To::childValue
}class From {
var grandChildValue: String?
}
class To {
var child: Child = Child()
class Child {
var grandChild: GrandChild = GrandChild()
}
class GrandChild {
var value: String?
}
}val mapper = mapper<From, To> {
From::grandChildValue mappedTo To::child..To.Child::grandChild..To.GrandChild::value
}data class SimpleEntity(
val commaDelimitedString: String
)data class SimpleEntityDisplay(
val stringList: List<String> = emptyList()
)class StringToListMappingTransformer : MappingTransformer<String, List<String>> {
override fun transform(context: MappingTransformerContext<out String>): List<String>? {
return context.originalValue?.split(",")
}
}val mapper = mapper<SimpleEntity, SimpleEntityDisplay> {
SimpleEntity::commaDelimitedString mappedTo SimpleEntityDisplay::stringList withTransformer StringToListMappingTransformer::class
}val mapper = mapper<SimpleEntity, SimpleEntityDisplay> {
SimpleEntity::commaDelimitedString mappedTo SimpleEntityDisplay::stringList withTransformer {
it.originalValue?.split(",")
}
}val mapper = mapper<SimpleEntity, SimpleEntityDisplay> {
autoMap(AutoMappingStrategy.BY_NAME)
SimpleEntity::name mappedTo SimpleEntityDisplay::fullName
}data class SimpleEntity(
val name: String
)data class SimpleEntityDisplay(
val name: String = ""
)class NotBlankStringCondition : MappingCondition<String> {
override fun isValid(context: MappingConditionContext<String>): Boolean {
return !context.originalValue.isNullOrBlank()
}
}val mapper = mapper<SimpleEntity, SimpleEntityDisplay> {
SimpleEntity::name mappedTo SimpleEntityDisplay::name withCondition NotBlankStringCondition::class
}val mapper = mapper<SimpleEntity, SimpleEntityDisplay> {
SimpleEntity::name mappedTo SimpleEntityDisplay::name withCondition {
!it.originalValue.isNullOrBlank()
}
}data class User(
var firstName: String,
var lastName: String
)data class UserDisplay(
var firstName: String,
var lastName: String,
var fullName: String
)class UserUserDisplayDecorator : MappingDecorator<User, UserDisplay> {
override fun decorate(context: MappingDecoratorContext<User, UserDisplay>) {
val (from, to) = context
to.fullName = "${from.firstName} ${from.lastName}"
}
}val shapeShift = ShapeShiftBuilder()
.withMapping<User, UserDisplay> {
User::firstName mappedTo UserDisplay::firstName
User::lastName mappedTo UserDisplay::lastName
decorate(UserUserDisplayDecorator())
}
.build()val shapeShift = ShapeShiftBuilder()
.withMapping<User, UserDisplay> {
User::firstName mappedTo UserDisplay::firstName
User::lastName mappedTo UserDisplay::lastName
decorate {
val (from, to) = it
to.fullName = "${from.firstName} ${from.lastName}"
}
}
.build()val mapper = mapper<SimpleEntity, SimpleEntityDisplay> {
SimpleEntity::name mappedTo SimpleEntityDisplay::name overrideStrategy MappingStrategy.MAP_ALL
}To create a condition, create a new class implementing the MappingCondition<T> interface.
class NotBlankStringCondition : MappingCondition<String> {
override fun isValid(context: MappingConditionContext<String>): Boolean {
return !context.originalValue.isNullOrBlank()
}
}public class NotBlankStringCondition implements MappingCondition<String> {
@Override
public boolean isValid(@NonNull MappingConditionContext<String> context) {
return context.getOriginalValue
The condition above checks that a string is not null or blank. After creating the condition class, all that is left is to use the condition.
Adding conditions to fields is available in both DSL and annotation mapping. Conditions can be added only to fields with the same type as the condition.
The condition can be added to annotation mapping using the condition attribute.
We mapped the name field and added the condition. Now the name field will be mapped to SimpleEntityDisplay only if its value is not null or blank.
The condition can be added to field mapping using the withCondition function.
Using the DSL, conditions can also be added inline.
Again, the name field will be mapped to SimpleEntityDisplay only if its value is not null or blank.
The condition can be added to field mapping builder using the withCondition function.
Also with the builder, conditions can also be added inline.
data class SimpleEntity(
val name: String
)public class SimpleEntity {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}data class SimpleEntityDisplay(
val name: String = ""
)public class SimpleEntityDisplay {
private String name = "";
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}lastNamefullNameAdding decorators is available through the ShapeShiftBuilder class. Decorators can be added inline or as a separate class.
To create a decorator class implement the MappingDecorator interface.
class UserUserDisplayDecorator : MappingDecorator<User, UserDisplay> {
override fun decorate(context: MappingDecoratorContext<User, UserDisplay>) {
val (from, to) = context
to.fullName = "${
public class UserUserDisplayDecorator implements MappingDecorator<User, UserDisplay> {
@Override
public void decorate(@NonNull MappingDecoratorContext<User, UserDisplay> mappingDecoratorContext) {
And register it to the ShapeShift instance.
It is also possible to add the decorator logic inline.
data class User(
var firstName: String,
var lastName: String
)public class User {
private String firstName;
private String lastName;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}data class UserDisplay(
var firstName: String,
var lastName: String,
var fullName: String
)public class UserDisplay {
private String firstName;
private String lastName;
private String fullName;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getFullName() {
return fullName;
}
public void setFullName(String fullName) {
this.fullName = fullName;
}
}In this quick start guide, we'll review the simplest use-case for ShapeShift, a simple mapping between two classes.
We start by defining two classes, our source class SimpleEntity and our destination class SimpleEntityDisplay.
Due to the fact that ShapeShift uses reflection behind the scenes, destination classes should have a no arg constructor. Alternatively, you can also pass already-instantiated destination objects to the map method.
We can now start adding our annotations to the SimpleEntity class. In this example, we want to map the name and description fields to the name and description fields of the SimpleEntityDisplay class, but not the privateData field.
To achieve this, we will use the @MappedField annotation on both of these fields. Additionally, we will define @DefaultMappingTarget on the SimpleEntity class, which will indicate that all fields annotated with @MappedField that do not specify a target should be mapped to the SimpleEntityDisplay class.
To instantiate ShapeShift we use the ShapeShiftBuilder.
All that's left is to map the SimpleEntity instance to the SimpleEntityDisplay class.
Now let's write a simple test to check this scenario.
Additionally, we can also pass a destination instance to the map method, let's write a test to check this scenario as well.
You can check out the full example .
We hope this quick start guide has given you a glimpse of the simplicity and power of ShapeShift. There's much more to learn, and we encourage you to keep reading about all of the different options available by reading their respective API documentation.
Auto map fields without any boiler-plate code.
Auto mapping is used to reduce the amount of boiler-place code required to configure mapping between two classes.
We start with our two classes, our source class SimpleEntity and our destination class SimpleEntityDisplay.
data class SimpleEntity(
val id: String,
val name: String,
val birthDate: Date,
val email: String,
val telephone: String
public class SimpleEntity {
private String id;
private String name;
private Date birthDate;
private String email;
private String telephone;
data class SimpleEntityDisplay(
val id: String = "",
val fullName: String = "",
val birthDate: Long = 0,
public class SimpleEntityDisplay {
private String id = "";
private String name = "";
private long birthDate = 0;
Auto mapping supports 3 different strategies:
BY_NAME_AND_TYPE - When a field with the same name and type is available on both the source and destination classes. The field will be mapped automatically.
In our example the fields that will be mapped in this strategy are: id, email and telephone.
BY_NAME - When a field with the same name is available on both the source and destination classes, not necessarily of the same type. The field will be mapped automatically using .
When using the BY_NAME strategy all fields with the same name and different types must have registered default transformers for the corresponding types. Otherwise, a runtime exception will be thrown.
In our example the fields that will be mapped in this strategy are: id, email, telephone and birthDate. Note that a default transformer between Date and Long must be registered or a runtime exception will be thrown.
NONE - The default strategy. Disables auto mapping completely. All mappings should be added manually.
Auto mapping is available in both DSL and annotation mapping.
Auto mapping can be added using the @AutoMapping annotation.
Two things to note:
The name field is mapped manually because it has a different name in the target class.
The @AutoMapping annotation has two attributes:
target - If no target is added then the auto mapping will be configured to any target class. It is possible to add multiple
Auto mapping can be added using the autoMap function.
autoMap function receives the desired auto mapping strategy. It is possible to add any manual mapping to add/change mapping behavior.
Similar to the Kotlin DSL, auto mapping can be added using the autoMap function.
With the builder, it is also possible to add additional manual mappings to add/change mapping behavior.
Nested fields and multiple targets annotations mapping.
In this guide, we'll review mapping nested fields, as well as defining mappings to multiple targets.
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
)public class AdvancedChildEntity {
private String childName;
public String getChildName() {
return childName;
}
public void setChildName
Followed by our source class:
In this example, we will have two separate destination classes, AdvancedEntityDisplay and ReducedAdvancedEntityDisplay.
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
Finally, let's write two tests to verify that our mapping is working correctly for both targets
You can check out the full example .
Useful transformers for subclasses mapping.
In this guide, we'll review two new built in transformers added in ShapeShift version 0.0.7, ImplicitMappingTransformer and ImplicitCollectionMappingTransformer.
ImplicitMappingTransformer is used to transform subclasses mapped with ShapeShift. Lets look at the following example:
We have mapped Address
@DefaultMappingTarget(SimpleEntityDisplay::class)
data class SimpleEntity(
@MappedField(condition = NotBlankStringCondition::class)
val name: String,
)@DefaultMappingTarget(SimpleEntityDisplay.class)
public class SimpleEntity {
@MappedField(condition = NotBlankStringCondition.class)
private String name;
// Getters and Setters...
}val mapper = mapper<SimpleEntity, SimpleEntityDisplay> {
SimpleEntity::name mappedTo SimpleEntityDisplay::name withCondition NotBlankStringCondition::class
}val mapper = mapper<SimpleEntity, SimpleEntityDisplay> {
SimpleEntity::name mappedTo SimpleEntityDisplay::name withCondition {
!it.originalValue.isNullOrBlank()
}
}MappingDefinition mappingDefinition = new MappingDefinitionBuilder(SimpleEntity.class, SimpleEntityDisplay.class)
.mapField("name", "name")
.withCondition(NotBlankStringCondition.class)
.build();MappingDefinition mappingDefinition = new MappingDefinitionBuilder(SimpleEntity.class, SimpleEntityDisplay.class)
.mapField("name", "name")
.withCondition(context -> context.getOriginalValue() != null && !((String) context.getOriginalValue()).trim().isEmpty())
.build();val shapeShift = ShapeShiftBuilder()
.withDecorator(UserUserDisplayDecorator())
.build()ShapeShift shapeShift = new ShapeShiftBuilder()
.withDecorator(User.class, UserDisplay.class, new UserUserDisplayDecorator())
.build();val shapeShift = ShapeShiftBuilder()
.withDecorator(MappingDecorator<User, UserDisplay> {
val (from, to) = it
to.fullName = "${from.firstName} ${from.lastName}"
})
.build()ShapeShift shapeShift = new ShapeShiftBuilder()
.withDecorator(User.class, UserDisplay.class, mappingDecoratorContext -> {
User from = mappingDecoratorContext.getFrom();
UserDisplay to = mappingDecoratorContext.getTo();
to.setFullName(from.getFirstName() + " " + from.getLastName());
})
.build();data class SimpleEntity(
val name: String,
val description: String,
val privateData: String
)public class SimpleEntity {
private String name;
private String description;
private String privateData;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getPrivateData() {
return privateData;
}
public void setPrivateData(String privateData) {
this.privateData = privateData;
}
}data class SimpleEntityDisplay(
val name: String = "",
val description: String = ""
)public class SimpleEntityDisplay {
private String name = "";
private String description = "";
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}@AutoMappingstrategy - The auto mapping strategy. Default value NONE.
AdvancedEntityDisplayReducedAdvancedEntityDisplayAdvancedEntityDisplaysecondChildNamedata class AdvancedEntity(
val name: String,
val firstChild: AdvancedChildEntity,
val secondChild: AdvancedChildEntity
)public class AdvancedEntity {
private String name;
private AdvancedChildEntity firstChild;
private AdvancedChildEntity secondChild;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public AdvancedChildEntity getFirstChild() {
return firstChild;
}
public void setFirstChild(AdvancedChildEntity firstChild) {
this.firstChild = firstChild;
}
public AdvancedChildEntity getSecondChild() {
return secondChild;
}
public void setSecondChild(AdvancedChildEntity secondChild) {
this.secondChild = secondChild;
}
}data class AdvancedEntityDisplay(
val name: String = "",
val firstChildName: String = "",
val secondChildName: String = ""
)public class AdvancedEntityDisplay {
private String name = "";
private String firstChildName = "";
private String secondChildName = "";
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getFirstChildName() {
return firstChildName;
}
public void setFirstChildName(String firstChildName) {
this.firstChildName = firstChildName;
}
public String getSecondChildName() {
return secondChildName;
}
public void setSecondChildName(String secondChildName) {
this.secondChildName = secondChildName;
}
}@DefaultMappingTarget(SimpleEntityDisplay::class)
data class SimpleEntity(
@MappedField
val name: String,
@MappedField
val description: String,
val privateData: String
)@DefaultMappingTarget(SimpleEntityDisplay.class)
public class SimpleEntity {
@MappedField
private String name;
@MappedField
private String description;
private String privateData;
// Getters and Setters...
}val shapeShift = ShapeShiftBuilder().build()ShapeShift shapeShift = new ShapeShiftBuilder().build();val shapeShift = ShapeShiftBuilder().build()
val simpleEntity = SimpleEntity("test", "test description", "private data")
val simpleEntityDisplay = shapeShift.map<SimpleEntityDisplay>(simpleEntity)ShapeShift shapeShift = new ShapeShiftBuilder().build();
SimpleEntity simpleEntity = new SimpleEntity();
simpleEntity.setName("test");
simpleEntity.setDescription("test description");
simpleEntity.setPrivateData("private data");
SimpleEntityDisplay simpleEntityDisplay = shapeShift.map(simpleEntity, SimpleEntityDisplay.class);@Test
internal fun `test simple mapping`() {
val shapeShift = ShapeShiftBuilder().build()
val simpleEntity = SimpleEntity("test", "test description", "private data")
val result = shapeShift.map<SimpleEntityDisplay>(simpleEntity)
expectThat(result.name)
.isEqualTo("test")
expectThat(result.description)
.isEqualTo("test description")
}@Test
public void testSimpleMapping() {
ShapeShift shapeShift = new ShapeShiftBuilder().build();
SimpleEntity simpleEntity = new SimpleEntity();
simpleEntity.setName("test");
simpleEntity.setDescription("test description");
simpleEntity.setPrivateData("private data");
SimpleEntityDisplay simpleEntityDisplay = shapeShift.map(simpleEntity, SimpleEntityDisplay.class);
assertEquals(simpleEntityDisplay.getName(), "test");
assertEquals(simpleEntityDisplay.getDescription(), "test description");
}@Test
internal fun `test simple mapping with premade destination instance`() {
val shapeShift = ShapeShiftBuilder().build()
val simpleEntity = SimpleEntity("test", "test description", "private data")
val result = shapeShift.map(simpleEntity, SimpleEntityDisplay())
expectThat(result.name)
.isEqualTo("test")
expectThat(result.description)
.isEqualTo("test description")
}@Test
public void testSimpleMappingWithPremadeDestinationInstance() {
ShapeShift shapeShift = new ShapeShiftBuilder().build();
SimpleEntity simpleEntity = new SimpleEntity();
simpleEntity.setName("test");
simpleEntity.setDescription("test description");
simpleEntity.setPrivateData("private data");
SimpleEntityDisplay simpleEntityDisplay = shapeShift.map(simpleEntity, new SimpleEntityDisplay());
assertEquals(simpleEntityDisplay.getName(), "test");
assertEquals(simpleEntityDisplay.getDescription(), "test description");
}@AutoMapping(SimpleEntityDisplay::class, strategy = AutoMappingStrategy.BY_NAME)
@DefaultMappingTarget(SimpleEntityDisplay::class)
data class SimpleEntity(
val id: String,
@MappedField(mapTo = "fullName")
val name: String,
val birthDate: Date,
val email: String,
val telephone: String
)@AutoMapping(target = SimpleEntityDisplay.class, strategy = AutoMappingStrategy.BY_NAME)
@DefaultMappingTarget(SimpleEntityDisplay.class)
public class SimpleEntity {
private String id;
@MappedField(mapTo = "fullName")
private String name;
private Date birthDate;
private String email;
private String telephone;
// Getters and Setters...
}val mapper = mapper<SimpleEntity, SimpleEntityDisplay> {
autoMap(AutoMappingStrategy.BY_NAME)
SimpleEntity::name mappedTo SimpleEntityDisplay::fullName
}MappingDefinition mappingDefinition = new MappingDefinitionBuilder(SimpleEntity.class, SimpleEntityDisplay.class)
.autoMap(AutoMappingStrategy.BY_NAME)
.mapField("name", "fullName")
.build();data class ReducedAdvancedEntityDisplay(
val name: String = "",
val firstChildName: String = ""
)public class ReducedAdvancedEntityDisplay {
private String name = "";
private String firstChildName = "";
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getFirstChildName() {
return firstChildName;
}
public void setFirstChildName(String firstChildName) {
this.firstChildName = firstChildName;
}
}
@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
)@DefaultMappingTarget(AdvancedEntityDisplay.class)
public class AdvancedEntity {
@MappedField
@MappedField(target = ReducedAdvancedEntityDisplay.class)
private String name;
@MappedField(mapFrom = "childName", mapTo = "firstChildName")
@MappedField(target = ReducedAdvancedEntityDisplay.class, mapFrom = "childName", mapTo = "firstChildName")
private AdvancedChildEntity firstChild;
@MappedField(mapFrom = "childName", mapTo = "secondChildName")
private AdvancedChildEntity secondChild;
// Getters and Setters...
}@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")
}@Test
public void testAdvancedMappingForAdvancedEntityDisplay() {
ShapeShift shapeShift = new ShapeShiftBuilder().build();
AdvancedEntity advancedEntity = new AdvancedEntity();
advancedEntity.setName("test");
AdvancedChildEntity firstChild = new AdvancedChildEntity();
firstChild.setChildName("first child");
advancedEntity.setFirstChild(firstChild);
AdvancedChildEntity secondChild = new AdvancedChildEntity();
secondChild.setChildName("second child");
advancedEntity.setSecondChild(secondChild);
AdvancedEntityDisplay result = shapeShift.map(advancedEntity, AdvancedEntityDisplay.class);
assertEquals(result.getName(), "test");
assertEquals(result.getFirstChildName(), "first child");
assertEquals(result.getSecondChildName(), "second child");
}
@Test
public void testAdvancedMappingForReducedAdvancedEntityDisplay() {
ShapeShift shapeShift = new ShapeShiftBuilder().build();
AdvancedEntity advancedEntity = new AdvancedEntity();
advancedEntity.setName("test");
AdvancedChildEntity firstChild = new AdvancedChildEntity();
firstChild.setChildName("first child");
advancedEntity.setFirstChild(firstChild);
AdvancedChildEntity secondChild = new AdvancedChildEntity();
secondChild.setChildName("second child");
advancedEntity.setSecondChild(secondChild);
ReducedAdvancedEntityDisplay result = shapeShift.map(advancedEntity, ReducedAdvancedEntityDisplay.class);
assertEquals(result.getName(), "test");
assertEquals(result.getFirstChildName(), "first child");
}AddressDisplay@DefaultMappingTarget(AddressDisplay::class)
class Address {
@MappedField
var country: String? = null
@MappedField
var city: String? = null
@DefaultMappingTarget(AddressDisplay::class)
public class Address {
@MappedField
private String country;
@MappedField
private String city;
@
We can use ShapeShift to map Address to AddressDisplay. But what we do if Address is a subclass? We use the ImplicitMappingTransformer.
The ImplicitMappingTransformer uses the ShapeShift instance internally to map Address to AddressDisplay.
ImplicitCollectionMappingTransformer has the same job as ImplicitMappingTransformer but for collections.
Java builder based usage of the ShapeShift library.
We start by defining two classes, our source class SimpleEntity and our destination class SimpleEntityDisplay.
We can now create a simple mapper. In this example, we want to map the name and description fields of SimpleEntity to the name and description fields of the SimpleEntityDisplay class, but not the privateData field.
To instantiate ShapeShift we use the ShapeShiftBuilder and register our mapper:
All that's left is to map the SimpleEntity instance to the SimpleEntityDisplay class.
In the example above we did basic mapping between fields. But what if we want to map from/to deep fields of child classes?
In order to access child classes we can use the full path of a field. Let's look at the following example:
We want to map the value field in Child class inside the From class to the childValue field in the To class. We will use the full path of value which is child.value.
The full path is supported in both source and destination fields, it also supports multi level depth (e.g. x.y.z).
Field transformers are a way to transform a field from one type to another when mapping it to a destination class. More about the ins-and-outs of transformers is available here:
The withTransformer function has 2 options to use transformers. Let's look at the following classes.
We want to map the commaDelimitedString field to the stringList field and change the field type from String to List<String> while doing so. To accomplish that we will use a transformer.
Our first option is to create a transformer class, StringToListMappingTransformer;
All we need to do to use our transformer is to pass it to the withTransformer function.
Our second option is to use an inline transformer. When we don't need to reuse a transformer we can just add its logic to the builder.
Auto mapping is used to reduce the amount of boiler-place code required to configure mapping between two classes. More info about auto mapping is available here:
Auto mapping can be added using the autoMap function.
autoMap function receives the desired . It is possible to add any manual mapping to add/change mapping behavior.
Conditions are used to determine wether a field should be mapped according to certain logic. More info about conditions is available here:
Let's look at the following classes.
We want to map the name field only if it's not null or blank. The withCondition function has 2 options to add conditions.
Our first option is to create a condition class. The condition receives context with the original value of the field and checks that it is not null or blank.
We will create our mapper and add the condition.
Our second option is to use an inline condition. When we don't need to reuse a condition we can just add its logic to the builder.
Decorators allow to add additional logic to the mapping operation. More info about conditions is available here:
Let's look at the following classes.
We want to merge the firstName and lastName fields to the fullName field in addition to mapping them to their respectable fields.
Decorators can be added inline or as a separate class.
To create a decorator class implement the MappingDecorator interface.
And register it to the ShapeShift instance.
It is also possible to add the decorator logic inline.
Due to the fact that ShapeShift uses reflection behind the scenes, destination classes need a no arg constructor. But in some cases you have no control over the destination classes and cannot modify them to add a no arg constructor. This is where Object Suppliers comes into play, you can register object suppliers to the ShapeShift instance to add your own logic for instance generation.
More info about Object Suppliers is available here:
The overrideStrategy function allows you to override the default mapping strategy configured on the ShapeShift instance.
More info about mapping strategy is available here:
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:
Let's first create our custom StringToListMappingTransformer;
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 , and the StringToListTransformer as a normal transformer.
When 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 .
Examples for default transformers: AnyToStringMappingTransformer, DateToLongMappingTransformer.
To exclude the out of the box default transformers just call the excludeDefaultTransformers when creating the ShapeShift instance.
We can now add our annotations;
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.
The DSL also supports inline transformer. When we don't need to reuse a transformer we can just add its logic to the DSL.
We can create the same mapping with the builder using the withTransformer function.
As the DSL, the builder also supports inline transformer.
Let's write a test to verify that our mapping is correct;
You can check out the full example .
@DefaultMappingTarget(UserDisplay::class)
class User {
@MappedField
var name: String? = null
@MappedField(transformer = ImplicitMappingTransformer::class)
var address: Address? = null
}
class UserDisplay {
var name: String? = null
var address: AddressDisplay? = null
}@DefaultMappingTarget(UserDisplay::class)
public class User {
@MappedField
private String name;
@MappedField(transformer = ImplicitMappingTransformer.class)
private Address address;
// Getters and Setters...
}
public class UserDisplay {
private String name;
private AddressDisplay address;
// Getters and Setters...
}@DefaultMappingTarget(UserDisplay::class)
class User {
@MappedField
var name: String? = null
@MappedField(transformer = ImplicitCollectionMappingTransformer::class)
var addresses: List<Address>? = null
}
class UserDisplay {
var name: String? = null
var addresses: List<AddressDisplay>? = null
}@DefaultMappingTarget(UserDisplay::class)
public class User {
@MappedField
private String name;
@MappedField(transformer = ImplicitCollectionMappingTransformer.class)
private List<Address> addresses;
// Getters and Setters...
}
public class UserDisplay {
private String name;
private List<AddressDisplay> addresses;
// Getters and Setters...
}public class SimpleEntity {
private String name;
private String description;
private String privateData;
public SimpleEntity() {
}
public SimpleEntity(String name, String description, String privateData) {
this.name = name;
this.description = description;
this.privateData = privateData;
}
// Getters and Setters...
}public class SimpleEntityDisplay {
private String name;
private String description;
public SimpleEntityDisplay() {
}
// Getters and Setters...
}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;
}
}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;
}
}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;
}
}MappingDefinition mappingDefinition = new MappingDefinitionBuilder(SimpleEntity.class, SimpleEntityDisplay.class)
.mapField("name", "name")
.mapField("description", "description")
.build();ShapeShift shapeShift = new ShapeShiftBuilder()
.withMapping(mappingDefinition)
.build();SimpleEntity simpleEntity = new SimpleEntity("test", "test description", "private data");
SimpleEntityDisplay simpleEntityDisplay = shapeShift.map(simpleEntity, SimpleEntityDisplay.class);public class From {
private Child child = new Child();
// Getters and Setters...
class Child {
private String value;
// Getters and Setters...
}
}
public class To {
private String childValue;
// Getters and Setters...
}MappingDefinition mappingDefinition = new MappingDefinitionBuilder(From.class, To.class)
.mapField("child.value", "childValue")
.build();public class SimpleEntity {
private String commaDelimitedString;
public SimpleEntity() {
}
public SimpleEntity(String commaDelimitedString) {
this.commaDelimitedString = commaDelimitedString;
}
// Getters and Setters...
}public class SimpleEntityDisplay {
private List<String> stringList;
public SimpleEntityDisplay() {
}
// Getters and Setters...
}
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;
}
}MappingDefinition mappingDefinition = new MappingDefinitionBuilder(SimpleEntity.class, SimpleEntityDisplay.class)
.mapField("commaDelimitedString", "stringList")
.withTransformer(StringToListMappingTransformer.class)
.build();MappingDefinition mappingDefinition = new MappingDefinitionBuilder(SimpleEntity.class, SimpleEntityDisplay.class)
.mapField("commaDelimitedString", "stringList")
.withTransformer(context -> context.getOriginalValue() != null
? Arrays.asList(((String) context.getOriginalValue()).split(","))
: null)
.build();MappingDefinition mappingDefinition = new MappingDefinitionBuilder(SimpleEntity.class, SimpleEntityDisplay.class)
.autoMap(AutoMappingStrategy.BY_NAME)
.mapField("name", "fullName")
.build();public class SimpleEntity {
private String name;
public SimpleEntity() {
}
// Getters and Setters...
}public class SimpleEntityDisplay {
private String name;
public SimpleEntityDisplay() {
}
// Getters and Setters...
}public class NotBlankStringCondition implements MappingCondition<String> {
@Override
public boolean isValid(@NonNull MappingConditionContext<String> context) {
return context.getOriginalValue() != null && !context.getOriginalValue().trim().isEmpty();
}
}MappingDefinition mappingDefinition = new MappingDefinitionBuilder(SimpleEntity.class, SimpleEntityDisplay.class)
.mapField("name", "name")
.withCondition(NotBlankStringCondition.class)
.build();MappingDefinition mappingDefinition = new MappingDefinitionBuilder(SimpleEntity.class, SimpleEntityDisplay.class)
.mapField("name", "name")
.withCondition(context -> context.getOriginalValue() != null && !((String) context.getOriginalValue()).trim().isEmpty())
.build();public class User {
private String firstName;
private String lastName;
// Getters and Setters...
}public class UserDisplay {
private String firstName;
private String lastName;
private String fullName;
// Getters and Setters...
}public class UserUserDisplayDecorator implements MappingDecorator<User, UserDisplay> {
@Override
public void decorate(@NonNull MappingDecoratorContext<User, UserDisplay> mappingDecoratorContext) {
User from = mappingDecoratorContext.getFrom();
UserDisplay to = mappingDecoratorContext.getTo();
to.setFullName(from.getFirstName() + " " + from.getLastName());
}
}aShapeShift shapeShift = new ShapeShiftBuilder()
.withDecorator(User.class, UserDisplay.class, new UserUserDisplayDecorator())
.build();ShapeShift shapeShift = new ShapeShiftBuilder()
.withDecorator(User.class, UserDisplay.class, mappingDecoratorContext -> {
User from = mappingDecoratorContext.getFrom();
UserDisplay to = mappingDecoratorContext.getTo();
to.setFullName(from.getFirstName() + " " + from.getLastName());
})
.build();MappingDefinition mappingDefinition = new MappingDefinitionBuilder(SimpleEntity.class, SimpleEntityDisplay.class)
.mapField("name", "name").withMappingStrategy(MappingStrategy.MAP_ALL)
.build();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();val shapeShift = ShapeShiftBuilder()
.excludeDefaultTransformers()
.build()ShapeShift shapeShift = new ShapeShiftBuilder()
.excludeDefaultTransformers()
.build();@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...
}val mapper = mapper<SimpleEntity, SimpleEntityDisplay> {
SimpleEntity::creationDate mappedTo SimpleEntityDisplay::creationDate
SimpleEntity::commaDelimitedString mappedTo SimpleEntityDisplay::stringList withTransformer StringToListMappingTransformer::class
}val mapper = mapper<SimpleEntity, SimpleEntityDisplay> {
SimpleEntity::creationDate mappedTo SimpleEntityDisplay::creationDate
SimpleEntity::commaDelimitedString mappedTo SimpleEntityDisplay::stringList withTransformer {
it.originalValue?.split(",")
}
}MappingDefinition mappingDefinition = new MappingDefinitionBuilder(SimpleEntity.class, SimpleEntityDisplay.class)
.mapField("creationDate", "creationDate")
.mapField("commaDelimitedString", "stringList")
.withTransformer(StringToListMappingTransformer.class)
.build();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
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"));
}public class SimpleEntityDisplay {
private String name = "";
private String description = "";
public String getName() {
We can now start adding our annotations to the SimpleEntity class. In this example, we want to map the name and description fields to the name and description fields of the SimpleEntityDisplay class, but not the privateData field.
To achieve this, we will use the @MappedField annotation on both of these fields. Additionally, we will define @DefaultMappingTarget on the SimpleEntity class, which will indicate that all fields annotated with @MappedField that do not specify a target should be mapped to the SimpleEntityDisplay class.
@DefaultMappingTarget(SimpleEntityDisplay::class)
data class SimpleEntity(
@MappedField
val name: String,
@MappedField
val description: String,
val privateData: String
@DefaultMappingTarget(SimpleEntityDisplay.class)
public class SimpleEntity {
@MappedField
private String name;
@MappedField
private String description;
private
@DefaultMappingTarget on a class, indicates that all fields annotated with @MappedField that do not specify a target should be mapped to this class by default.
The mapping target comes into play when you want to map a single source to multiple destinations. The target attribute is used to indicate to which class the field should be mapped. If no target is specified the target will be determined by the @DefaultMappingTarget on the class.
In the above code name is mapped once to the SimpleEntityDisplay class using the default mapping target, and once to the SimpleEntityExport class using the target attribute.
By default each @MappedField is mapped to a field with the same name in the target class. To change it use the mapTo value. For example:
The field name to map the value from.
When mapFrom is used at the field level, it allows for mapping of nested values.
As you can see above, we use the mapFrom attribute to access the childName field in AdvancedChildEntity.
And we use the mapTo attribute to map the values to the appropriate fields in AdvancedEntityDisplay.
The @MappedField annotation can also be used at the type level. When used at the type level, the mapFrom attribute is required to indicate the name of the field to use, if left empty an exception will be thrown.
Field transformers are a way to transform a field from one type to another when mapping it to a destination class. More about the ins-and-outs of transformers is available here:
To configure a transformer for a field use the transformer attribute. The transformer attribute receives the transformer's class.
Auto mapping is used to reduce the amount of boiler-place code required to configure mapping between two classes. More info about auto mapping is available here:
Auto mapping can be added using the @AutoMapping annotation.
The @AutoMapping annotation has two attributes:
target - If no target is added then the auto mapping will be configured to any target class. It is possible to add multiple @AutoMapping annotation for multiple target classes.
strategy - The auto mapping strategy. Default value NONE.
Conditions are used to determine wether a field should be mapped according to certain logic. More info about conditions is available here:
A condition can be added to annotation mapping using the condition attribute.
We mapped the name field and added a condition for the mapping.
The condition receives context with the original value of the field and checks that it is not null or blank.
The overrideMappingStrategy attribute allows you to override the default mapping strategy configured on the ShapeShift instance.
More info about mapping strategy is available here:
data class SimpleEntity(
val name: String,
val description: String,
val privateData: String
)public class SimpleEntity {
private String name;
private String description;
private String privateData;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getPrivateData() {
return privateData;
}
public void setPrivateData(String privateData) {
this.privateData = privateData;
}
}data class SimpleEntityDisplay(
val name: String = "",
val description: String = ""
)@DefaultMappingTarget(SimpleEntityDisplay::class)
data class SimpleEntity(
@MappedField
@MappedField(target = SimpleEntityExport::class)
val name: String,
@MappedField
val description: String
)@DefaultMappingTarget(SimpleEntityDisplay.class)
public class SimpleEntity {
@MappedField
@MappedField(target = SimpleEntityExport.class)
private String name;
@MappedField
private String description;
// Getters and Setters...
}data class SimpleEntityDisplay(
val name: String = "",
val description: String = ""
)public class SimpleEntityDisplay {
private String name = "";
private String description = "";
// Getters and Setters...
}data class SimpleEntityExport(
val name: String = ""
)public class SimpleEntityExport {
private String name = "";
// Getters and Setters...
}@DefaultMappingTarget(SimpleEntityDisplay::class)
data class SimpleEntity(
@MappedField(mapTo = "fullName")
val name: String
)@DefaultMappingTarget(SimpleEntityDisplay.class)
public class SimpleEntity {
@MappedField(mapTo = "fullName")
private String name;
// Getters and Setters...
}data class SimpleEntityDisplay(
val fullName: String = ""
)public class SimpleEntityDisplay {
private String fullName = "";
// Getters and Setters...
}@DefaultMappingTarget(AdvancedEntityDisplay::class)
data class AdvancedEntity(
// This field will be mapped to the "firstChildName" field in the default target class
@MappedField(mapFrom = "childName", mapTo = "firstChildName")
val firstChild: AdvancedChildEntity,
// This field will be mapped to the "secondChildName" field in the default target class
@MappedField(mapFrom = "childName", mapTo = "secondChildName")
val secondChild: AdvancedChildEntity
)@DefaultMappingTarget(AdvancedEntityDisplay.class)
public class AdvancedEntity {
// This field will be mapped to the "firstChildName" field in the default target class
@MappedField(mapFrom = "childName", mapTo = "firstChildName")
private AdvancedChildEntity firstChild;
// This field will be mapped to the "secondChildName" field in the default target class
@MappedField(mapFrom = "childName", mapTo = "secondChildName")
private AdvancedChildEntity secondChild;
// Getters and Setters...
}data class AdvancedChildEntity(
val childName: String
)public class AdvancedChildEntity {
private String childName;
// Getters and Setters...
}data class AdvancedEntityDisplay(
val firstChildName: String = "",
val secondChildName: String = ""
)public class AdvancedEntityDisplay {
private String firstChildName = "";
private String secondChildName = "";
// Getters and Setters...
}@MappedField(target = SimpleEntityDisplay::class, mapFrom = "firstName")
@MappedField(target = SimpleEntityDisplay::class, mapFrom = "lastName")
data class SimpleEntity(
val firstName: String,
val lastName: String,
)@MappedField(target = SimpleEntityDisplay.class, mapFrom = "firstName")
@MappedField(target = SimpleEntityDisplay.class, mapFrom = "lastName")
public class SimpleEntity {
private String firstName;
private String lastName;
// Getters and Setters...
}data class SimpleEntityDisplay(
val firstName: String = "",
val lastName: String = ""
)public class SimpleEntityDisplay {
private String firstName = "";
private String lastName = "";
// Getters and Setters...
}@DefaultMappingTarget(SimpleEntityDisplay::class)
data class SimpleEntity(
@MappedField(transformer = StringToListMappingTransformer::class, mapTo = "stringList")
val commaDelimitedString: String
)@DefaultMappingTarget(SimpleEntityDisplay.class)
public class SimpleEntity {
@MappedField(transformer = StringToListMappingTransformer.class, mapTo = "stringList")
private String commaDelimitedString;
// Getters and Setters...
}data class SimpleEntityDisplay(
val stringList: List<String> = emptyList()
)public class SimpleEntityDisplay {
private List<String> stringList = new ArrayList<>();
// Getters and Setters...
}@AutoMapping(SimpleEntityDisplay::class, strategy = AutoMappingStrategy.BY_NAME)
@DefaultMappingTarget(SimpleEntityDisplay::class)
data class SimpleEntity(
val id: String,
val name: String,
val birthDate: Date,
val email: String,
val telephone: String
)@AutoMapping(target = SimpleEntityDisplay.class, strategy = AutoMappingStrategy.BY_NAME)
@DefaultMappingTarget(SimpleEntityDisplay.class)
public class SimpleEntity {
private String id;
private String name;
private Date birthDate;
private String email;
private String telephone;
// Getters and Setters...
}@DefaultMappingTarget(SimpleEntityDisplay::class)
data class SimpleEntity(
@MappedField(condition = NotBlankStringCondition::class)
val name: String
)@DefaultMappingTarget(SimpleEntityDisplay.class)
public class SimpleEntity {
@MappedField(condition = NotBlankStringCondition.class)
private String name;
// Getters and Setters...
}data class SimpleEntityDisplay(
val name: String = ""
)public class SimpleEntityDisplay {
private String name = "";
// Getters and Setters...
}class NotBlankStringCondition : MappingCondition<String> {
override fun isValid(context: MappingConditionContext<String>): Boolean {
return !context.originalValue.isNullOrBlank()
}
}public class NotBlankStringCondition implements MappingCondition<String> {
@Override
public boolean isValid(@NonNull MappingConditionContext<String> context) {
return context.getOriginalValue() != null && !context.getOriginalValue().trim().isEmpty();
}
}@DefaultMappingTarget(SimpleEntityDisplay::class)
data class SimpleEntity(
@MappedField(overrideMappingStrategy = MappingStrategy.MAP_ALL)
val name: String
)@DefaultMappingTarget(SimpleEntityDisplay.class)
public class SimpleEntity {
@MappedField(overrideMappingStrategy = MappingStrategy.MAP_ALL)
private String name;
// Getters and Setters...
}