-
Notifications
You must be signed in to change notification settings - Fork 0
Configuration
ZMapper uses a fluent API to configure mappings. This page covers all available configuration options.
The CreateMap<TSource, TDestination>() method registers a mapping between two types:
config.CreateMap<Source, Destination>();Properties with matching names are mapped automatically (convention-based mapping).
Use ForMember when source and destination property names differ, or when you need custom transformation logic:
config.CreateMap<OrderDto, Order>()
.ForMember(dest => dest.OrderTotal,
opt => opt.MapFrom(src => src.Items.Sum(i => i.Price)));Map from nested objects using navigation properties:
config.CreateMap<InvoiceEntity, InvoiceDto>()
// Flatten: src.Client.CompanyName -> dest.ClientName
.ForMember(dest => dest.ClientName,
opt => opt.MapFrom(src => src.Client != null ? src.Client.CompanyName : ""));Handle nullable source values with fallback defaults:
config.CreateMap<InvoiceEntity, InvoiceDto>()
// DateTime? -> DateTime with fallback
.ForMember(dest => dest.IssueDate,
opt => opt.MapFrom(src => src.IssueDate ?? DateTime.UtcNow));Apply transformations inline:
config.CreateMap<UserDto, User>()
.ForMember(dest => dest.EmailAddress,
opt => opt.MapFrom(src => src.Email.ToUpper()));Prevent specific destination properties from being mapped:
config.CreateMap<UserDto, User>()
.ForMember(dest => dest.PasswordHash, opt => opt.Ignore())
.ForMember(dest => dest.InternalId, opt => opt.Ignore());Ignored properties retain their default values (null, 0, false, etc.).
By default, ZMapper emits a compile-time warning (ZMAP001) for destination properties without a matching source:
warning ZMAP001: Destination property 'Address' on type 'Destination'
has no matching source property on 'Source'.
You have two options:
config.CreateMap<Source, Destination>()
.ForMember(dest => dest.Address, opt => opt.Ignore());config.CreateMap<Source, Destination>()
.IgnoreNonExisting(); // All non-matching properties silently keep defaultsThis is especially useful when hooks (BeforeMap/AfterMap) set destination-only properties:
config.CreateMap<InvoiceDto, Invoice>()
.IgnoreNonExisting() // CreatedAt, ProcessedBy set by hooks
.BeforeMap((src, dest) => dest.CreatedAt = DateTime.UtcNow)
.AfterMap((src, dest) => dest.ProcessedBy = "ZMapper");Map a property only when a condition is met:
config.CreateMap<ProductDto, Product>()
.ForMember(dest => dest.Price, opt =>
{
opt.MapFrom(src => src.Price);
opt.When(src => src.Price > 0); // Only map positive prices
})
.ForMember(dest => dest.Description, opt =>
{
opt.MapFrom(src => src.Description!);
opt.When(src => src.Description != null); // Only map non-null
});When the condition evaluates to false, the destination property keeps its default value.
Create mappings in both directions with a single call:
config.CreateMap<OrderDto, Order>()
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.OrderId))
.ForMember(dest => dest.Customer, opt => opt.MapFrom(src => src.CustomerName))
.ReverseMap();
// Now both OrderDto -> Order AND Order -> OrderDto work
var order = mapper.Map<OrderDto, Order>(orderDto);
var dto = mapper.Map<Order, OrderDto>(order);Note:
ReverseMap()automatically invertsForMemberconfigurations. Ignored members are excluded from the reverse mapping.IgnoreNonExisting()is propagated to the reverse direction.
Execute custom logic before or after mapping:
config.CreateMap<InvoiceDto, Invoice>()
.ForMember(dest => dest.InvoiceId, opt => opt.MapFrom(src => src.Id))
.BeforeMap((src, dest) =>
{
// Runs BEFORE property mapping
dest.CreatedAt = DateTime.UtcNow;
})
.AfterMap((src, dest) =>
{
// Runs AFTER property mapping
dest.ProcessedBy = "ZMapper";
dest.TotalWithTax = dest.Total * 1.21m;
});Common use cases:
- Setting audit fields (timestamps, user info)
- Computing derived values after mapping
- Logging or validation
- Normalizing data before mapping
ZMapper automatically maps properties from the entire inheritance chain — no extra configuration needed:
public abstract class BaseEntity
{
public int Id { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
}
public class Product : BaseEntity
{
public string Name { get; set; }
public decimal Price { get; set; }
}
// Id, CreatedAt, UpdatedAt from BaseEntity are mapped automatically
config.CreateMap<ProductDto, Product>();This works with any inheritance depth (e.g., BaseEntity -> AuditableEntity -> Product).
When the source has nullable value types and the destination has non-nullable types, ZMapper safely maps using ?? default:
// Source: DateTime? IssueDate -> Destination: DateTime IssueDate
// Generated code: destination.IssueDate = source.IssueDate ?? default;
config.CreateMap<NullableSource, NonNullableDestination>();Map into an already-constructed object instead of creating a new one:
var existingUser = GetFromDatabase();
mapper.Map<UserDto, User>(dto, existingUser);
// existingUser's properties are updated in-placeAll configuration methods return the mapping expression, so you can chain them fluently:
config.CreateMap<OrderDto, Order>()
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.OrderId))
.ForMember(dest => dest.Customer, opt => opt.MapFrom(src => src.CustomerName))
.ForMember(dest => dest.InternalNote, opt => opt.Ignore())
.IgnoreNonExisting()
.BeforeMap((src, dest) => dest.CreatedAt = DateTime.UtcNow)
.AfterMap((src, dest) => dest.Total = dest.Items.Sum(i => i.Price))
.ReverseMap();- Profiles and Dependency Injection — Organize mappings into profiles
- Advanced Features — Hooks, conditional mapping, nested objects
- API Reference — Complete method reference
ZMapper v1.2.1 | GitHub Repository | Report an Issue | MIT License