Skip to main content

User-implemented mapping methods

If Mapperly cannot generate a mapping, one can implement it manually simply by providing a method body in the mapper declaration:

[Mapper]
public partial class CarMapper
{
public partial CarDto CarToCarDto(Car car);

private int TimeSpanToHours(TimeSpan t) => t.Hours;
}

Whenever Mapperly needs a mapping from TimeSpan to int inside the CarMapper implementation, it will use the provided implementation. The types of the user-implemented mapping method need to match the types to map exactly, including nullability.

If there are multiple user-implemented mapping methods suiting the given type-pair, by default, the first one is used. This can be customized by using automatic user-implemented mapping method discovery and default mapping method.

Automatic user-implemented mapping method discovery​

By default, user implemented mapping methods are discovered automatically. This can be disabled by setting AutoUserMappings to false. With AutoUserMappings disabled, only methods marked by the UserMappingAttribute are discovered by Mapperly.

[Mapper(AutoUserMappings = false)]
public partial class CarMapper
{
public partial CarDto CarToCarDto(Car car);

// mark as user-implemented mapping
[UserMapping]
private int TimeSpanToHours(TimeSpan t) => t.Hours;

private int IgnoredTimeSpanToHours(TimeSpan t) => t.Minutes / 60;
}

The AutoUserMappings value also applies to the usage of external mappers: With AutoUserMappings enabled all methods with a mapping method signature are discovered. With AutoUserMappings disabled, only methods with a UserMappingAttribute and, if the containing class has a MapperAttribute, partial methods are discovered.

Ignoring a user-implemented mapping method​

To ignore a user-implemented mapping method with enabled AutoUserMappings, [UserMapping(Ignore = true)] can be applied.

[Mapper]
public partial class CarMapper
{
public partial CarDto CarToCarDto(Car car);

// discovered and used by default (AutoUserMappings is true by default)
private int TimeSpanToHours(TimeSpan t) => t.Hours;

// ignored user-implemented mapping
[UserMapping(Ignore = true)]
private int IgnoredTimeSpanToHours(TimeSpan t) => t.Minutes / 60;
}

Default mapping methods​

Whenever Mapperly will need a mapping for a given type-pair, it will use the default mapping. A user-implemented or user-defined mapping is considered the default mapping for a type-pair if Default = true is set on the UserMapping attribute. If no user-implemented mapping with Default = true exists and AutoUserMappings is enabled, the first user-implemented mapping which has an unspecified Default value is used. For each type-pair only one default mapping can exist.

If multiple mappings exist for the same type-pair without a default mapping, RMG060 is reported. By default RMG060 has a severity of warning, which can be changed using the .editorconfig.

Map properties using user-implemented mappings​

See user-implemented property conversions. The mapper will respect the ref keyword on the target parameter when using an existing target function, allowing the reference to be updated in the caller.

  [Mapper(AllowNullPropertyAssignment = false, UseDeepCloning = true, RequiredMappingStrategy = RequiredMappingStrategy.Source)]
public static partial class UseUserMethodWithRef
{
[MapProperty(nameof(ArrayObject.IntArray), nameof(ArrayObject.IntArray), Use = nameof(MapArray))] // `Use` is required otherwise it will generate it's own
public static partial void Merge([MappingTarget] ArrayObject target, ArrayObject second);

private static void MapArray([MappingTarget] ref int[] target, int[] second) => target = [.. target, .. second.Except(target)];
}