or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

bean-factory.mdbuilder-configuration.mdcore-mapping.mdcustom-conversion.mdevent-system.mdindex.mdmetadata-access.mdprogrammatic-mapping.md

custom-conversion.mddocs/

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

```