0
# Custom Conversion System
1
2
Extensible converter framework for handling complex type transformations and custom mapping logic that goes beyond Dozer's automatic property mapping capabilities.
3
4
## Capabilities
5
6
### Basic Custom Converter Interface
7
8
Base interface for implementing custom conversion logic.
9
10
```java { .api }
11
/**
12
* Public custom converter interface for custom data mapping logic
13
*/
14
public interface CustomConverter {
15
/**
16
* Converts source field value to destination field value
17
* @param existingDestinationFieldValue current value of destination field (may be null)
18
* @param sourceFieldValue value from source field
19
* @param destinationClass target class type
20
* @param sourceClass source class type
21
* @return converted value for destination field
22
*/
23
Object convert(Object existingDestinationFieldValue, Object sourceFieldValue,
24
Class<?> destinationClass, Class<?> sourceClass);
25
}
26
```
27
28
**Usage Example:**
29
30
```java
31
import com.github.dozermapper.core.CustomConverter;
32
import com.github.dozermapper.core.ConversionException;
33
import java.text.SimpleDateFormat;
34
import java.text.ParseException;
35
import java.util.Date;
36
37
public class DateToStringConverter implements CustomConverter {
38
@Override
39
public Object convert(Object existingDestinationFieldValue, Object sourceFieldValue,
40
Class<?> destinationClass, Class<?> sourceClass) {
41
if (sourceFieldValue == null) {
42
return null;
43
}
44
45
if (sourceFieldValue instanceof Date && destinationClass == String.class) {
46
return new SimpleDateFormat("yyyy-MM-dd").format((Date) sourceFieldValue);
47
}
48
49
if (sourceFieldValue instanceof String && destinationClass == Date.class) {
50
try {
51
return new SimpleDateFormat("yyyy-MM-dd").parse((String) sourceFieldValue);
52
} catch (ParseException e) {
53
throw new ConversionException("Invalid date format: " + sourceFieldValue, e);
54
}
55
}
56
57
throw new ConversionException("Unsupported conversion from " +
58
sourceClass.getName() + " to " + destinationClass.getName());
59
}
60
}
61
```
62
63
### Configurable Custom Converter
64
65
Extended converter interface that can receive configuration parameters.
66
67
```java { .api }
68
/**
69
* Custom converter that can receive configuration parameters
70
*/
71
public interface ConfigurableCustomConverter extends CustomConverter {
72
/**
73
* Sets configuration parameter for the converter
74
* @param parameter configuration string
75
*/
76
void setParameter(String parameter);
77
}
78
```
79
80
**Usage Example:**
81
82
```java
83
import com.github.dozermapper.core.ConfigurableCustomConverter;
84
import java.text.SimpleDateFormat;
85
86
public class DateFormatConverter implements ConfigurableCustomConverter {
87
private String dateFormat = "yyyy-MM-dd"; // default
88
89
@Override
90
public void setParameter(String parameter) {
91
this.dateFormat = parameter;
92
}
93
94
@Override
95
public Object convert(Object existingDestinationFieldValue, Object sourceFieldValue,
96
Class<?> destinationClass, Class<?> sourceClass) {
97
// Use this.dateFormat for conversion
98
SimpleDateFormat formatter = new SimpleDateFormat(dateFormat);
99
// ... conversion logic
100
return null; // Placeholder for actual conversion logic
101
}
102
}
103
```
104
105
### Type-Safe Dozer Converter Base Class
106
107
Abstract base class providing type-safe bidirectional conversion.
108
109
```java { .api }
110
/**
111
* Base class for implementing type-safe bidirectional custom converters
112
* @param <A> first type for conversion
113
* @param <B> second type for conversion
114
*/
115
public abstract class DozerConverter<A, B> implements ConfigurableCustomConverter {
116
/**
117
* Constructor specifying the types this converter handles
118
* @param prototypeA class of type A
119
* @param prototypeB class of type B
120
*/
121
public DozerConverter(Class<A> prototypeA, Class<B> prototypeB);
122
123
/**
124
* Convert from type A to type B
125
* @param source source object of type A
126
* @param destination existing destination object of type B (may be null)
127
* @return converted object of type B
128
*/
129
public abstract B convertTo(A source, B destination);
130
131
/**
132
* Convert from type B to type A
133
* @param source source object of type B
134
* @param destination existing destination object of type A (may be null)
135
* @return converted object of type A
136
*/
137
public abstract A convertFrom(B source, A destination);
138
139
/**
140
* Convert from type A to type B (creates new instance)
141
* @param source source object of type A
142
* @return new converted object of type B
143
*/
144
public B convertTo(A source);
145
146
/**
147
* Convert from type B to type A (creates new instance)
148
* @param source source object of type B
149
* @return new converted object of type A
150
*/
151
public A convertFrom(B source);
152
153
/**
154
* Sets configuration parameter for the converter
155
* @param parameter configuration string
156
*/
157
public void setParameter(String parameter);
158
159
/**
160
* Gets the configuration parameter
161
* @return configuration string or null if not set
162
*/
163
public String getParameter();
164
}
165
```
166
167
**Usage Example:**
168
169
```java
170
public class MoneyToStringConverter extends DozerConverter<Money, String> {
171
172
public MoneyToStringConverter() {
173
super(Money.class, String.class);
174
}
175
176
@Override
177
public String convertTo(Money source, String destination) {
178
if (source == null) return null;
179
return source.getAmount() + " " + source.getCurrency();
180
}
181
182
@Override
183
public Money convertFrom(String source, Money destination) {
184
if (source == null) return null;
185
String[] parts = source.split(" ");
186
return new Money(new BigDecimal(parts[0]), parts[1]);
187
}
188
}
189
```
190
191
### Mapper Aware Interface
192
193
Interface allowing converters to receive mapper instance injection for recursive mapping.
194
195
```java { .api }
196
/**
197
* Allows custom converters to receive mapper instance injection
198
*/
199
public interface MapperAware {
200
/**
201
* Injects the mapper instance into the converter
202
* @param mapper the mapper instance
203
*/
204
void setMapper(Mapper mapper);
205
}
206
```
207
208
**Usage Example:**
209
210
```java
211
public class PersonToPersonDtoConverter extends DozerConverter<Person, PersonDto>
212
implements MapperAware {
213
private Mapper mapper;
214
215
public PersonToPersonDtoConverter() {
216
super(Person.class, PersonDto.class);
217
}
218
219
@Override
220
public void setMapper(Mapper mapper) {
221
this.mapper = mapper;
222
}
223
224
@Override
225
public PersonDto convertTo(Person source, PersonDto destination) {
226
if (source == null) return null;
227
228
PersonDto result = destination != null ? destination : new PersonDto();
229
result.setFullName(source.getFirstName() + " " + source.getLastName());
230
231
// Use injected mapper for nested objects
232
if (source.getAddress() != null) {
233
result.setAddress(mapper.map(source.getAddress(), AddressDto.class));
234
}
235
236
return result;
237
}
238
239
@Override
240
public Person convertFrom(PersonDto source, Person destination) {
241
// Reverse conversion logic
242
return null; // Implementation details...
243
}
244
}
245
```
246
247
## Converter Registration
248
249
### Global Converters
250
251
Register converters to be used for all applicable type combinations:
252
253
```java
254
Mapper mapper = DozerBeanMapperBuilder.create()
255
.withCustomConverter(new DateToStringConverter())
256
.withCustomConverter(new MoneyToStringConverter())
257
.build();
258
```
259
260
### ID-Based Converters
261
262
Register converters with IDs for specific mapping configurations:
263
264
```java
265
Mapper mapper = DozerBeanMapperBuilder.create()
266
.withCustomConverterWithId("dateConverter", new DateFormatConverter())
267
.withCustomConverterWithId("moneyConverter", new MoneyToStringConverter())
268
.build();
269
```
270
271
Then reference in XML mapping:
272
273
```xml
274
<field>
275
<a>dateField</a>
276
<b>stringField</b>
277
<a-converter type="dateConverter" parameter="MM/dd/yyyy" />
278
</field>
279
```
280
281
## Built-in Converter Types
282
283
Dozer includes several built-in converters for common scenarios:
284
285
### Date Converters
286
- `DateConverter`: Handles various Date type conversions
287
- `CalendarConverter`: Calendar to other date type conversions
288
- `InstantConverter`: Java 8 Instant conversions
289
290
### Numeric Converters
291
- `IntegerConverter`: Integer and int conversions
292
- Built-in support for all primitive and wrapper numeric types
293
294
### Enum Converter
295
- `EnumConverter`: Automatic enum to string and vice versa
296
297
## Advanced Conversion Patterns
298
299
### Conditional Conversion
300
301
```java
302
public class ConditionalConverter implements CustomConverter {
303
@Override
304
public Object convert(Object existingDestinationFieldValue, Object sourceFieldValue,
305
Class<?> destinationClass, Class<?> sourceClass) {
306
307
// Only convert if certain conditions are met
308
if (shouldConvert(sourceFieldValue, destinationClass)) {
309
return performConversion(sourceFieldValue, destinationClass);
310
}
311
312
// Return existing value or null
313
return existingDestinationFieldValue;
314
}
315
316
private boolean shouldConvert(Object source, Class<?> destClass) {
317
// Custom logic to determine if conversion should occur
318
return source != null && isValidForConversion(source);
319
}
320
}
321
```
322
323
### Collection Element Conversion
324
325
```java
326
public class CollectionElementConverter extends DozerConverter<List<String>, List<Integer>> {
327
328
public CollectionElementConverter() {
329
super(List.class, List.class);
330
}
331
332
@Override
333
public List<Integer> convertTo(List<String> source, List<Integer> destination) {
334
if (source == null) return null;
335
336
List<Integer> result = new ArrayList<>();
337
for (String str : source) {
338
result.add(Integer.valueOf(str));
339
}
340
return result;
341
}
342
343
@Override
344
public List<String> convertFrom(List<Integer> source, List<String> destination) {
345
if (source == null) return null;
346
347
List<String> result = new ArrayList<>();
348
for (Integer num : source) {
349
result.add(num.toString());
350
}
351
return result;
352
}
353
}
354
```
355
356
## Exception Handling
357
358
Custom converters should handle errors appropriately:
359
360
```java { .api }
361
/**
362
* Exception thrown during conversion operations
363
*/
364
public class ConversionException extends MappingException {
365
public ConversionException(String message);
366
public ConversionException(String message, Throwable cause);
367
public ConversionException(Throwable cause);
368
}
369
```
370
371
**Usage in Converters:**
372
373
```java
374
@Override
375
public Object convert(Object existingDestinationFieldValue, Object sourceFieldValue,
376
Class<?> destinationClass, Class<?> sourceClass) {
377
try {
378
// Conversion logic here
379
return performConversion(sourceFieldValue);
380
} catch (Exception e) {
381
throw new ConversionException("Failed to convert " + sourceFieldValue +
382
" to " + destinationClass.getSimpleName(), e);
383
}
384
}
385
```
386
387
## Best Practices
388
389
### Performance Optimization
390
- Make converters stateless when possible
391
- Cache expensive computations within converters
392
- Avoid creating new instances unnecessarily
393
394
### Error Handling
395
- Validate input parameters before conversion
396
- Provide meaningful error messages in exceptions
397
- Handle null values explicitly
398
399
### Type Safety
400
- Use `DozerConverter<A, B>` for type safety when possible
401
- Validate types before casting in generic converters
402
- Document supported type combinations clearly
403
404
## Custom Field Mapper
405
406
Global field mapping interface for intercepting all field mappings. Should be used very sparingly as it impacts performance.
407
408
```java { .api }
409
/**
410
* Public custom field mapper interface. A custom field mapper should only be used in very rare
411
* and unusual cases because it is invoked for ALL field mappings. For custom mappings of
412
* particular fields, using a CustomConverter is a much better choice.
413
*/
414
public interface CustomFieldMapper {
415
/**
416
* Custom field mapping logic invoked for all field mappings
417
* @param source source object
418
* @param destination destination object
419
* @param sourceFieldValue value from source field
420
* @param classMap internal class mapping metadata
421
* @param fieldMapping internal field mapping metadata
422
* @return true if field was handled, false to continue with normal mapping
423
*/
424
boolean mapField(Object source, Object destination, Object sourceFieldValue,
425
ClassMap classMap, FieldMap fieldMapping);
426
}
427
```
428
429
**Usage Example:**
430
431
```java
432
import com.github.dozermapper.core.CustomFieldMapper;
433
import com.github.dozermapper.core.classmap.ClassMap;
434
import com.github.dozermapper.core.fieldmap.FieldMap;
435
436
public class GlobalAuditFieldMapper implements CustomFieldMapper {
437
@Override
438
public boolean mapField(Object source, Object destination, Object sourceFieldValue,
439
ClassMap classMap, FieldMap fieldMapping) {
440
441
// Handle audit fields globally
442
if ("lastModified".equals(fieldMapping.getDestFieldName())) {
443
// Set current timestamp instead of source value
444
ReflectionUtils.setFieldValue(destination, "lastModified", new Date());
445
return true; // Field handled, skip normal mapping
446
}
447
448
if ("modifiedBy".equals(fieldMapping.getDestFieldName())) {
449
// Set current user instead of source value
450
ReflectionUtils.setFieldValue(destination, "modifiedBy", getCurrentUser());
451
return true; // Field handled
452
}
453
454
return false; // Continue with normal mapping
455
}
456
}
457
458
// Configuration
459
Mapper mapper = DozerBeanMapperBuilder.create()
460
.withCustomFieldMapper(new GlobalAuditFieldMapper())
461
.build();
462
```