High-performance C# object mapper using source generators with convention-based and attribute-based mapping support
$ dotnet add package Moedim.MapperC# Source Generator Object Mapper - Convention-based and Attribute-based mapping with compile-time code generation

This is a Hebrew word that translates "feast" or "appointed time." "Appointed times" refers to HaSham's festivals in Vayikra/Leviticus 23rd. The feasts are "signals and signs" to help us know what is on the heart of HaShem.
Please send email if you consider to hire me.
If you like or are using this project to learn or start your solution, please give it a star. Thanks!
dotnet add package Moedim.Mapper
using Moedim.Mapper;
// Source class
public class User
{
public string Name { get; set; }
public int Age { get; set; }
public string Email { get; set; }
}
// Destination class with MapFrom attribute
[MapFrom(typeof(User))]
public class UserDto
{
public string Name { get; set; }
public int Age { get; set; }
public string Email { get; set; }
}
// Usage
var user = new User { Name = "John", Age = 30, Email = "john@example.com" };
var dto = user.ToUserDto(); // Extension method generated automatically
public class Product
{
public string ProductName { get; set; }
public decimal Price { get; set; }
}
[MapFrom(typeof(Product))]
public class ProductDto
{
[MapProperty("ProductName")] // Map from different property name
public string Name { get; set; }
public decimal Price { get; set; }
}
// Usage
var product = new Product { ProductName = "Laptop", Price = 1299.99m };
var dto = product.ToProductDto();
// dto.Name will contain "Laptop"
public class Employee
{
public string Name { get; set; }
public decimal Salary { get; set; }
public string SocialSecurityNumber { get; set; }
}
[MapFrom(typeof(Employee))]
public class EmployeeDto
{
public string Name { get; set; }
[IgnoreProperty] // Excluded from mapping
public decimal Salary { get; set; }
[IgnoreProperty] // Excluded from mapping
public string SocialSecurityNumber { get; set; }
}
public class Team
{
public string Name { get; set; }
public List<string> Members { get; set; }
}
[MapFrom(typeof(Team))]
public class TeamDto
{
public string Name { get; set; }
public List<string> Members { get; set; }
}
// Collections are mapped automatically
var team = new Team
{
Name = "Dev Team",
Members = new List<string> { "Alice", "Bob" }
};
var dto = team.ToTeamDto();
// Map in both directions
[MapTo(typeof(PersonDto))]
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
[MapFrom(typeof(Person))]
public class PersonDto
{
public string Name { get; set; }
public int Age { get; set; }
}
// Now both directions work
var person = new Person { Name = "Alice", Age = 25 };
var dto = person.ToPersonDto();
var personAgain = dto.ToPerson();
Moedim.Mapper automatically handles nested complex objects:
public class Address
{
public string Street { get; set; }
public string City { get; set; }
public string ZipCode { get; set; }
}
[MapFrom(typeof(Address))]
public class AddressDto
{
public string Street { get; set; }
public string City { get; set; }
public string ZipCode { get; set; }
}
public class Customer
{
public string Name { get; set; }
public Address DeliveryAddress { get; set; }
}
[MapFrom(typeof(Customer))]
public class CustomerDto
{
public string Name { get; set; }
public AddressDto DeliveryAddress { get; set; }
}
// Nested objects are automatically mapped
var customer = new Customer
{
Name = "John Doe",
DeliveryAddress = new Address
{
Street = "123 Main St",
City = "New York",
ZipCode = "10001"
}
};
var dto = customer.ToCustomerDto();
// dto.DeliveryAddress will be an AddressDto with all properties mapped
Map collections containing complex objects:
public class OrderItem
{
public string ProductName { get; set; }
public int Quantity { get; set; }
public decimal Price { get; set; }
}
[MapFrom(typeof(OrderItem))]
public class OrderItemDto
{
public string ProductName { get; set; }
public int Quantity { get; set; }
public decimal Price { get; set; }
}
public class Order
{
public string OrderNumber { get; set; }
public List<OrderItem> Items { get; set; }
}
[MapFrom(typeof(Order))]
public class OrderDto
{
public string OrderNumber { get; set; }
public List<OrderItemDto> Items { get; set; }
}
// Collection items are automatically transformed
var order = new Order
{
OrderNumber = "ORD-001",
Items = new List<OrderItem>
{
new OrderItem { ProductName = "Laptop", Quantity = 1, Price = 1299.99m },
new OrderItem { ProductName = "Mouse", Quantity = 2, Price = 29.99m }
}
};
var dto = order.ToOrderDto();
// dto.Items will be a List<OrderItemDto> with all items mapped
Apply custom transformation logic to property values:
public class TemperatureReading
{
public double Celsius { get; set; }
public DateTime Timestamp { get; set; }
}
// Custom converter implementation
public class CelsiusToFahrenheitConverter : IValueConverter<double, double>
{
public double Convert(double celsius)
{
return (celsius * 9.0 / 5.0) + 32.0;
}
}
[MapFrom(typeof(TemperatureReading))]
public class TemperatureReadingDto
{
[ConvertWith(typeof(CelsiusToFahrenheitConverter))]
public double Celsius { get; set; } // Will contain Fahrenheit value
public DateTime Timestamp { get; set; }
}
// Usage
var reading = new TemperatureReading { Celsius = 25.0, Timestamp = DateTime.Now };
var dto = reading.ToTemperatureReadingDto();
// dto.Celsius will be 77.0 (Fahrenheit)
Handle complex, deeply nested object graphs:
public class Company
{
public string Name { get; set; }
public Department MainDepartment { get; set; }
public List<Department> AllDepartments { get; set; }
}
public class Department
{
public string Name { get; set; }
public Manager Manager { get; set; }
public List<Employee> Employees { get; set; }
}
public class Manager
{
public string Name { get; set; }
public int YearsExperience { get; set; }
}
public class Employee
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
// Corresponding DTOs with [MapFrom] attributes...
// All levels are automatically mapped
var company = new Company
{
Name = "TechCorp",
MainDepartment = new Department
{
Name = "Engineering",
Manager = new Manager { Name = "Alice", YearsExperience = 10 },
Employees = new List<Employee>
{
new Employee { FirstName = "Bob", LastName = "Smith" },
new Employee { FirstName = "Carol", LastName = "Jones" }
}
}
};
var dto = company.ToCompanyDto();
// Entire object graph is mapped recursively
For the basic User/UserDto example, Moedim.Mapper generates:
// <auto-generated/>
#nullable enable
using TestNamespace;
namespace TestNamespace;
/// <summary>
/// Extension methods for mapping User to UserDto.
/// </summary>
public static class UserToUserDtoMappingExtensions
{
/// <summary>
/// Maps User to UserDto.
/// </summary>
public static UserDto? ToUserDto(this User? source)
{
if (source is null)
return null;
return new UserDto
{
Name = source.Name,
Age = source.Age,
Email = source.Email
};
}
}
Moedim.Mapper uses source generators to create mapping code at compile time, resulting in:
Run benchmarks with:
dotnet run --project tests/Moedim.Mapper.Performance.Tests -c Release
The fluent configuration API provides a programmatic way to define mappings with full IntelliSense support and type safety.
using Moedim.Mapper;
var builder = new MapperConfigurationBuilder();
// Create a simple mapping
var mapping = builder.CreateMap<User, UserDto>();
// The mapping is now configured and ready to use
// Note: mapping.ForMember() returns IMappingExpression for chaining
Map properties with different names using the fluent API:
var builder = new MapperConfigurationBuilder();
builder.CreateMap<User, UserDto>()
.ForMember(dest => dest.FullName, opt => opt.MapFrom(src => src.Name))
.ForMember(dest => dest.EmailAddress, opt => opt.MapFrom(src => src.Email));
// Custom property mappings configured:
// - FullName ← Name
// - EmailAddress ← Email
Exclude specific properties from mapping:
var builder = new MapperConfigurationBuilder();
builder.CreateMap<User, UserDto>()
.ForMember(dest => dest.InternalId, opt => opt.Ignore())
.ForMember(dest => dest.Metadata, opt => opt.Ignore());
// InternalId and Metadata will be excluded from mapping
The fluent API supports method chaining for concise configuration:
var builder = new MapperConfigurationBuilder();
builder.CreateMap<Customer, CustomerDto>()
.ForMember(dest => dest.CustomerName, opt => opt.MapFrom(src => src.Name))
.ForMember(dest => dest.PrimaryEmail, opt => opt.MapFrom(src => src.Email))
.ForMember(dest => dest.InternalId, opt => opt.Ignore())
.ForMember(dest => dest.DisplayAddress, opt => opt.MapFrom(src => src.Address));
Configure multiple type mappings in a single builder:
var builder = new MapperConfigurationBuilder();
builder.CreateMap<User, UserDto>()
.ForMember(dest => dest.FullName, opt => opt.MapFrom(src => src.Name));
builder.CreateMap<Customer, CustomerDto>()
.ForMember(dest => dest.CustomerName, opt => opt.MapFrom(src => src.Name));
builder.CreateMap<Order, OrderDto>()
.ForMember(dest => dest.OrderNumber, opt => opt.MapFrom(src => src.Id));
// Multiple mappings are now configured in the builder
public interface IMapperConfigurationBuilder
{
IMappingExpression<TSource, TDestination> CreateMap<TSource, TDestination>();
}
public interface IMappingExpression<TSource, TDestination>
{
IMappingExpression<TSource, TDestination> ForMember<TMember>(
Expression<Func<TDestination, TMember>> destinationMember,
Action<IMemberConfigurationExpression<TSource, TDestination, TMember>> memberOptions);
}
public interface IMemberConfigurationExpression<TSource, TDestination, TMember>
{
void MapFrom(Expression<Func<TSource, TMember>> sourceMember);
void Ignore();
}
Mark configuration classes for the source generator:
[MapperConfiguration]
public class MyMappingConfiguration
{
public void Configure(IMapperConfigurationBuilder builder)
{
builder.CreateMap<User, UserDto>()
.ForMember(dest => dest.FullName, opt => opt.MapFrom(src => src.Name));
}
}
Moedim.Mapper provides both automated and manual migration options:
Install the migration tool globally:
dotnet tool install -g Moedim.Mapper.Migration.Tool
Analyze your project for AutoMapper usage:
moedim-mapper-migrate analyze path/to/YourProject.csproj --verbose
Migrate automatically to Moedim.Mapper:
moedim-mapper-migrate migrate path/to/YourProject.csproj
Generate a compatibility report:
moedim-mapper-migrate report path/to/YourProject.csproj --format html
For detailed tool documentation, see Migration Tool README.
For step-by-step manual migration guidance with code comparisons, see MIGRATION_GUIDE.md.
Contributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the MIT License - see the LICENSE file for details.
Built with:
[MapFrom] and [MapTo][MapProperty][IgnoreProperty][ConvertWith] and IValueConverter<TSource, TDest>List<SourceType> to List<DestType>