CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-com-github-dozermapper--dozer-core

Java Bean to Java Bean mapper that recursively copies data from one object to another

Pending
Overview
Eval results
Files

custom-conversion.mddocs/

Custom Conversion System

Extensible converter framework for handling complex type transformations and custom mapping logic that goes beyond Dozer's automatic property mapping capabilities.

Capabilities

Basic Custom Converter Interface

Base interface for implementing custom conversion logic.

/**
 * Public custom converter interface for custom data mapping logic
 */
public interface CustomConverter {
    /**
     * Converts source field value to destination field value
     * @param existingDestinationFieldValue current value of destination field (may be null)
     * @param sourceFieldValue value from source field
     * @param destinationClass target class type
     * @param sourceClass source class type
     * @return converted value for destination field
     */
    Object convert(Object existingDestinationFieldValue, Object sourceFieldValue, 
                  Class<?> destinationClass, Class<?> sourceClass);
}

Usage Example:

import com.github.dozermapper.core.CustomConverter;
import com.github.dozermapper.core.ConversionException;
import java.text.SimpleDateFormat;
import java.text.ParseException;
import java.util.Date;

public class DateToStringConverter implements CustomConverter {
    @Override
    public Object convert(Object existingDestinationFieldValue, Object sourceFieldValue,
                         Class<?> destinationClass, Class<?> sourceClass) {
        if (sourceFieldValue == null) {
            return null;
        }
        
        if (sourceFieldValue instanceof Date && destinationClass == String.class) {
            return new SimpleDateFormat("yyyy-MM-dd").format((Date) sourceFieldValue);
        }
        
        if (sourceFieldValue instanceof String && destinationClass == Date.class) {
            try {
                return new SimpleDateFormat("yyyy-MM-dd").parse((String) sourceFieldValue);
            } catch (ParseException e) {
                throw new ConversionException("Invalid date format: " + sourceFieldValue, e);
            }
        }
        
        throw new ConversionException("Unsupported conversion from " + 
            sourceClass.getName() + " to " + destinationClass.getName());
    }
}

Configurable Custom Converter

Extended converter interface that can receive configuration parameters.

/**
 * Custom converter that can receive configuration parameters
 */
public interface ConfigurableCustomConverter extends CustomConverter {
    /**
     * Sets configuration parameter for the converter
     * @param parameter configuration string
     */
    void setParameter(String parameter);
}

Usage Example:

import com.github.dozermapper.core.ConfigurableCustomConverter;
import java.text.SimpleDateFormat;

public class DateFormatConverter implements ConfigurableCustomConverter {
    private String dateFormat = "yyyy-MM-dd"; // default
    
    @Override
    public void setParameter(String parameter) {
        this.dateFormat = parameter;
    }
    
    @Override
    public Object convert(Object existingDestinationFieldValue, Object sourceFieldValue,
                         Class<?> destinationClass, Class<?> sourceClass) {
        // Use this.dateFormat for conversion
        SimpleDateFormat formatter = new SimpleDateFormat(dateFormat);
        // ... conversion logic
        return null; // Placeholder for actual conversion logic
    }
}

Type-Safe Dozer Converter Base Class

Abstract base class providing type-safe bidirectional conversion.

/**
 * Base class for implementing type-safe bidirectional custom converters
 * @param <A> first type for conversion
 * @param <B> second type for conversion
 */
public abstract class DozerConverter<A, B> implements ConfigurableCustomConverter {
    /**
     * Constructor specifying the types this converter handles
     * @param prototypeA class of type A
     * @param prototypeB class of type B
     */
    public DozerConverter(Class<A> prototypeA, Class<B> prototypeB);
    
    /**
     * Convert from type A to type B
     * @param source source object of type A
     * @param destination existing destination object of type B (may be null)
     * @return converted object of type B
     */
    public abstract B convertTo(A source, B destination);
    
    /**
     * Convert from type B to type A
     * @param source source object of type B
     * @param destination existing destination object of type A (may be null)
     * @return converted object of type A
     */
    public abstract A convertFrom(B source, A destination);
    
    /**
     * Convert from type A to type B (creates new instance)
     * @param source source object of type A
     * @return new converted object of type B
     */
    public B convertTo(A source);
    
    /**
     * Convert from type B to type A (creates new instance)
     * @param source source object of type B
     * @return new converted object of type A
     */
    public A convertFrom(B source);
    
    /**
     * Sets configuration parameter for the converter
     * @param parameter configuration string
     */
    public void setParameter(String parameter);
    
    /**
     * Gets the configuration parameter
     * @return configuration string or null if not set
     */
    public String getParameter();
}

Usage Example:

public class MoneyToStringConverter extends DozerConverter<Money, String> {
    
    public MoneyToStringConverter() {
        super(Money.class, String.class);
    }
    
    @Override
    public String convertTo(Money source, String destination) {
        if (source == null) return null;
        return source.getAmount() + " " + source.getCurrency();
    }
    
    @Override
    public Money convertFrom(String source, Money destination) {
        if (source == null) return null;
        String[] parts = source.split(" ");
        return new Money(new BigDecimal(parts[0]), parts[1]);
    }
}

Mapper Aware Interface

Interface allowing converters to receive mapper instance injection for recursive mapping.

/**
 * Allows custom converters to receive mapper instance injection
 */
public interface MapperAware {
    /**
     * Injects the mapper instance into the converter
     * @param mapper the mapper instance
     */
    void setMapper(Mapper mapper);
}

Usage Example:

public class PersonToPersonDtoConverter extends DozerConverter<Person, PersonDto> 
                                        implements MapperAware {
    private Mapper mapper;
    
    public PersonToPersonDtoConverter() {
        super(Person.class, PersonDto.class);
    }
    
    @Override
    public void setMapper(Mapper mapper) {
        this.mapper = mapper;
    }
    
    @Override
    public PersonDto convertTo(Person source, PersonDto destination) {
        if (source == null) return null;
        
        PersonDto result = destination != null ? destination : new PersonDto();
        result.setFullName(source.getFirstName() + " " + source.getLastName());
        
        // Use injected mapper for nested objects
        if (source.getAddress() != null) {
            result.setAddress(mapper.map(source.getAddress(), AddressDto.class));
        }
        
        return result;
    }
    
    @Override
    public Person convertFrom(PersonDto source, Person destination) {
        // Reverse conversion logic
        return null; // Implementation details...
    }
}

Converter Registration

Global Converters

Register converters to be used for all applicable type combinations:

Mapper mapper = DozerBeanMapperBuilder.create()
    .withCustomConverter(new DateToStringConverter())
    .withCustomConverter(new MoneyToStringConverter())
    .build();

ID-Based Converters

Register converters with IDs for specific mapping configurations:

Mapper mapper = DozerBeanMapperBuilder.create()
    .withCustomConverterWithId("dateConverter", new DateFormatConverter())
    .withCustomConverterWithId("moneyConverter", new MoneyToStringConverter())
    .build();

Then reference in XML mapping:

<field>
    <a>dateField</a>
    <b>stringField</b>
    <a-converter type="dateConverter" parameter="MM/dd/yyyy" />
</field>

Built-in Converter Types

Dozer includes several built-in converters for common scenarios:

Date Converters

  • DateConverter: Handles various Date type conversions
  • CalendarConverter: Calendar to other date type conversions
  • InstantConverter: Java 8 Instant conversions

Numeric Converters

  • IntegerConverter: Integer and int conversions
  • Built-in support for all primitive and wrapper numeric types

Enum Converter

  • EnumConverter: Automatic enum to string and vice versa

Advanced Conversion Patterns

Conditional Conversion

public class ConditionalConverter implements CustomConverter {
    @Override
    public Object convert(Object existingDestinationFieldValue, Object sourceFieldValue,
                         Class<?> destinationClass, Class<?> sourceClass) {
        
        // Only convert if certain conditions are met
        if (shouldConvert(sourceFieldValue, destinationClass)) {
            return performConversion(sourceFieldValue, destinationClass);
        }
        
        // Return existing value or null
        return existingDestinationFieldValue;
    }
    
    private boolean shouldConvert(Object source, Class<?> destClass) {
        // Custom logic to determine if conversion should occur
        return source != null && isValidForConversion(source);
    }
}

Collection Element Conversion

public class CollectionElementConverter extends DozerConverter<List<String>, List<Integer>> {
    
    public CollectionElementConverter() {
        super(List.class, List.class);
    }
    
    @Override
    public List<Integer> convertTo(List<String> source, List<Integer> destination) {
        if (source == null) return null;
        
        List<Integer> result = new ArrayList<>();
        for (String str : source) {
            result.add(Integer.valueOf(str));
        }
        return result;
    }
    
    @Override
    public List<String> convertFrom(List<Integer> source, List<String> destination) {
        if (source == null) return null;
        
        List<String> result = new ArrayList<>();
        for (Integer num : source) {
            result.add(num.toString());
        }
        return result;
    }
}

Exception Handling

Custom converters should handle errors appropriately:

/**
 * Exception thrown during conversion operations
 */
public class ConversionException extends MappingException {
    public ConversionException(String message);
    public ConversionException(String message, Throwable cause);
    public ConversionException(Throwable cause);
}

Usage in Converters:

@Override
public Object convert(Object existingDestinationFieldValue, Object sourceFieldValue,
                     Class<?> destinationClass, Class<?> sourceClass) {
    try {
        // Conversion logic here
        return performConversion(sourceFieldValue);
    } catch (Exception e) {
        throw new ConversionException("Failed to convert " + sourceFieldValue + 
            " to " + destinationClass.getSimpleName(), e);
    }
}

Best Practices

Performance Optimization

  • Make converters stateless when possible
  • Cache expensive computations within converters
  • Avoid creating new instances unnecessarily

Error Handling

  • Validate input parameters before conversion
  • Provide meaningful error messages in exceptions
  • Handle null values explicitly

Type Safety

  • Use DozerConverter<A, B> for type safety when possible
  • Validate types before casting in generic converters
  • Document supported type combinations clearly

Custom Field Mapper

Global field mapping interface for intercepting all field mappings. Should be used very sparingly as it impacts performance.

/**
 * Public custom field mapper interface. A custom field mapper should only be used in very rare 
 * and unusual cases because it is invoked for ALL field mappings. For custom mappings of 
 * particular fields, using a CustomConverter is a much better choice.
 */
public interface CustomFieldMapper {
    /**
     * Custom field mapping logic invoked for all field mappings
     * @param source source object
     * @param destination destination object  
     * @param sourceFieldValue value from source field
     * @param classMap internal class mapping metadata
     * @param fieldMapping internal field mapping metadata
     * @return true if field was handled, false to continue with normal mapping
     */
    boolean mapField(Object source, Object destination, Object sourceFieldValue, 
                    ClassMap classMap, FieldMap fieldMapping);
}

Usage Example:

import com.github.dozermapper.core.CustomFieldMapper;
import com.github.dozermapper.core.classmap.ClassMap;
import com.github.dozermapper.core.fieldmap.FieldMap;

public class GlobalAuditFieldMapper implements CustomFieldMapper {
    @Override
    public boolean mapField(Object source, Object destination, Object sourceFieldValue,
                           ClassMap classMap, FieldMap fieldMapping) {
        
        // Handle audit fields globally
        if ("lastModified".equals(fieldMapping.getDestFieldName())) {
            // Set current timestamp instead of source value
            ReflectionUtils.setFieldValue(destination, "lastModified", new Date());
            return true; // Field handled, skip normal mapping
        }
        
        if ("modifiedBy".equals(fieldMapping.getDestFieldName())) {
            // Set current user instead of source value  
            ReflectionUtils.setFieldValue(destination, "modifiedBy", getCurrentUser());
            return true; // Field handled
        }
        
        return false; // Continue with normal mapping
    }
}

// Configuration
Mapper mapper = DozerBeanMapperBuilder.create()
    .withCustomFieldMapper(new GlobalAuditFieldMapper())
    .build();

Install with Tessl CLI

npx tessl i tessl/maven-com-github-dozermapper--dozer-core

docs

bean-factory.md

builder-configuration.md

core-mapping.md

custom-conversion.md

event-system.md

index.md

metadata-access.md

programmatic-mapping.md

tile.json