<?xml version="1.0" encoding="UTF-8"?>
<mappings xmlns="http://dozermapper.github.io/schema/bean-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://dozermapper.github.io/schema/bean-mapping http://dozermapper.github.io/schema/bean-mapping.xsd">
<configuration>
<custom-converters>
<!-- these are always bi-directional -->
<converter type="com.github.dozermapper.core.converters.TestCustomConverter">
<class-a>com.github.dozermapper.core.vo.CustomDoubleObject</class-a>
<class-b>java.lang.Double</class-b>
</converter>
<!-- You are responsible for mapping everything between
ClassA and ClassB -->
<converter type="com.github.dozermapper.core.converters.TestCustomHashMapConverter">
<class-a>
com.github.dozermapper.core.vo.TestCustomConverterHashMapObject
</class-a>
<class-b>
com.github.dozermapper.core.vo.TestCustomConverterHashMapPrimeObject
</class-b>
</converter>
</custom-converters>
</configuration>
</mappings>
Custom Converters
Custom converters are used to perform custom mapping between two objects. In the Configuration block, you can add some XML to tell Dozer to use a custom converter for certain class A and class B types. When a custom converter is specified for a class A and class B combination, Dozer will invoke the custom converter to perform the data mapping instead of the standard mapping logic.
Your custom converter must implement the com.github.dozermapper.core.CustomConverter
interface in order for Dozer to accept it. Otherwise an exception will
be thrown.
Custom converters are shared across mapping files. This means that you can define them once in a mapping file and it will be applied to all class mappings in other mapping files that match the Class A - Class B pattern. In the example below, whenever Dozer comes across a mapping where the src/dest class match the custom converter definition, it will invoke the custom converter class instead of performing the typical mapping.
Custom converters can also be specified at the individual field level. In the example below, Dozer will invoke the custom converter to perform the field mapping.
<mapping>
<class-a>com.github.dozermapper.core.vo.SimpleObj</class-a>
<class-b>com.github.dozermapper.core.vo.SimpleObjPrime2</class-b>
<field custom-converter="com.github.dozermapper.core.converters.StringAppendCustomConverter">
<a>field1</a>
<b>field1Prime</b>
</field>
</mapping>
Custom converter 'instances' can be reused at the individual field level. In the example below, Dozer will invoke the custom converter to perform the field mapping.
<mapping>
<class-a>com.github.dozermapper.core.vo.SimpleObj</class-a>
<class-b>com.github.dozermapper.core.vo.SimpleObjPrime2</class-b>
<field custom-converter-id="CustomConverterWithId">
<a>field1</a>
<b>field1Prime</b>
</field>
</mapping>
CustomConverter
instances can be provided during configuration of Mapper
via
DozerBeanMapperBuilder#withCustomConverter(..)
method.
<?xml version="1.0" encoding="UTF-8"?>
<beans default-lazy-init="false">
<bean id="com.github.dozermapper.core.Mapper" class="com.github.dozermapper.core.DozerBeanMapper">
<property name="mappingFiles">
<list>
<value>dozerBeanMapping.xml</value>
<value>injectedCustomConverter.xml</value>
</list>
</property>
<property name="customConvertersWithId">
<map>
<entry key="CustomConverterWithId" ref="configurableConverterBeanInstance1" />
<entry key="CustomConverterWithId2" ref="configurableConverterBeanInstance2" />
</map>
</property>
</bean>
</beans>
Sample custom converter implementation:
Note: Custom Converters get invoked when the source value is null
, so
you need to explicitly handle null
values in your custom converter
implementation.
import com.github.dozermapper.core.CustomConverter;
import com.github.dozermapper.core.MappingException;
public class TestCustomConverter implements CustomConverter {
@Override
public Object convert(Object existingDestinationFieldValue, Object sourceFieldValue, Class<?> destinationClass, Class<?> sourceClass) {
if (sourceFieldValue == null) {
return null;
}
CustomDoubleObject dest;
if (source instanceof Double) {
// check to see if the object already exists
if (destination == null) {
dest = new CustomDoubleObject();
} else {
dest = (CustomDoubleObject)destination;
}
dest.setTheDouble(((Double)source).doubleValue());
return dest;
} else if (source instanceof CustomDoubleObject) {
double sourceObj = ((CustomDoubleObject)source).getTheDouble();
return new Double(sourceObj);
} else {
throw new MappingException("Converter TestCustomConverter "
+ "used incorrectly. Arguments passed in were:"
+ destination
+ " and "
+ source);
}
}
}
CustomConverters can also be injected into the Mapper
if you
need to do some manipulation with them before they are used in dozer.
<?xml version="1.0" encoding="UTF-8"?>
<beans default-lazy-init="false">
<bean id="com.github.dozermapper.core.Mapper" class="com.github.dozermapper.core.DozerBeanMapper">
<property name="mappingFiles">
<list>
<value>dozerBeanMapping.xml</value>
<value>injectedCustomConverter.xml</value>
</list>
</property>
<property name="customConverters">
<list>
<ref bean="customConverterTest" />
</list>
</property>
</bean>
<!-- custom converter -->
<bean id="customConverterTest" class="com.github.dozermapper.core.converters.InjectedCustomConverter">
<property name="injectedName">
<value>injectedName</value>
</property>
</bean>
</beans>
Support for Array Types
You can specify a custom converter for Array types. For example, if you
want to use a custom converter for mapping between an array of objects
and a String you would use the following mapping notation. Dozer
generically uses ClassLoader.loadClass()
when parsing the mapping files.
For arrays, java expects the class name in the following format:
[Lcom.github.dozermapper.core.vo.SimpleObj
;
<converter type="com.github.dozermapper.core.converters.StringAppendCustomConverter">
<class-a>[Lcom.github.dozermapper.core.vo.SimpleObj;</class-a>
<class-b>java.lang.String</class-b>
</converter>
Support for primitives
You can specify a custom converter for primitive types. Just use the
primitive wrapper class when defining the custom converter mapping. In
the following example, Dozer will use the specified custom converter
when mapping between SomeObject and the int primitive type. Note that
Dozer will also use the custom converter when mapping between SomeObject
and the Integer
wrapper type.
<converter type="somePackage.SomeCustomConverter">
<class-a>somePackage.SomeObject</class-a>
<class-b>java.lang.Integer</class-b>
</converter>
Configurable Custom Converters
You can define a custom converter, which can be configured from mappings
via configuration parameter. In this case you should implement
ConfigurableCustomConverter
interface instead of usual CustomConverter
.
Configurable converter has additional attribute provided in runtime -
param. Parameter is provided using custom-converter-param
attribute.
<mapping>
<class-a>com.github.dozermapper.core.vo.BeanA</class-a>
<class-b>com.github.dozermapper.core.vo.BeanB</class-b>
<field custom-converter="com.github.dozermapper.core.converters.MathOperationConverter" custom-converter-param="+">
<a>amount</a>
<b>amount</b>
</field>
</mapping>
Configurable custom converter should be used when you have similar behaviour in many cases, which can be parametrized, but the number of combinations is too high to do simple Custom Converter subclassing.
import com.github.dozermapper.core.ConfigurableCustomConverter;
import com.github.dozermapper.core.MappingException;
public class MathOperationConverter implements ConfigurableCustomConverter {
private String parameter;
@Override
public void setParameter(String parameter) {
this.parameter = parameter;
}
@Override
public Object convert(Object existingDestinationFieldValue, Object sourceFieldValue, Class<?> destinationClass, Class<?> sourceClass) {
Integer source = (Integer)sourceFieldValue;
Integer destination = (Integer)existingDestinationFieldValue;
if ("+".equals(parameter)) {
return destination.intValue + source.intValue();
}
if ("-".equals(parameter)) {
return destination.intValue - source.intValue();
}
throw new MappingException("Converter MathOperationConverter "
+ "used incorrectly. Arguments passed in were:"
+ destination
+ ", "
+ source
+ " and "
+ parameter);
}
}
New Custom Converter API
While providing great deal of flexibility Custom Converter API described above is written on fairly low level of abstraction. This results in converter, which code is difficult to understand and to reuse in other ways than plugging into Dozer mapping. However it is not uncommon situation when the same conversion logic should be called from a place other than bean mapping framework. version of Dozer gets shipped with new - cleaner API for defining custom converter, which gives you more obvious API while taking away certain part of control of the executions flow. The following example demonstrates simple, yet working converter using new API.
import com.github.dozermapper.core.DozerConverter;
public class NewDozerConverter extends DozerConverter<String, Boolean> {
public NewDozerConverter() {
super(String.class, Boolean.class);
}
@Override
public Boolean convertTo(String source, Boolean destination) {
if ("yes".equals(source)) {
return Boolean.TRUE;
} else if ("no".equals(source)) {
return Boolean.FALSE;
}
throw new IllegalStateException("Unknown value!");
}
@Override
public String convertFrom(Boolean source, String destination) {
if (Boolean.TRUE.equals(source)) {
return "yes";
} else if (Boolean.FALSE.equals(source)) {
return "no";
}
throw new IllegalStateException("Unknown value!");
}
}
Note that Java 5 Generics are supported and you do not need to cast source object to desired type as previously.
Data Structure Conversions
There are cases where it is required to perform programmatic data
structure conversion, say copy each odd element in a list as map key,
but each even as map value. In this case it is needed to define
transformation of the structure while relying on usual Dozer mapping
support for individual values. For this purposes it is possible to use
MapperAware
interface, which injects current mapper instance inside
custom converter.
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.github.dozermapper.core.DozerConverter;
import com.github.dozermapper.core.Mapper;
import com.github.dozermapper.core.MapperAware;
public class Converter extends DozerConverter<List, Map> implements MapperAware {
private Mapper mapper;
public Converter() {
super(List.class, Map.class);
}
@Override
public void setMapper(Mapper mapper) {
this.mapper = mapper;
}
@Override
public Map convertTo(List source, Map destination) {
Map originalToMapped = new HashMap();
for (Source item : source) {
Target mappedItem = mapper.map(item, Target.class);
originalToMapped.put(item, mappedItem);
}
return originalToMapped;
}
@Override
public List convertFrom(Map source, List destination) {
throw new IllegalStateException("Not implemented");
}
}