Zero-reflection, compile-time object mapper for .NET.
IronMapper generates all mapping code at build time using a Roslyn Source Generator — no runtime reflection, no IL emit, no dynamic proxies. Just plain, readable, debuggable C# that the compiler can optimize like any other code.
| Feature | AutoMapper | IronMapper |
|---|---|---|
| Mapping strategy | Reflection at runtime | Source-generated C# at compile time |
| Misconfiguration detected | At app startup | At dotnet build |
| NativeAOT / IL trimming | Requires annotations | Fully compatible |
| Performance | ~2–5 µs / object | ~0 ns overhead |
| License | MIT | MIT |
| API style | Profile-based fluent API | Attributes or Profile-based fluent API |
| Package size | ~500 KB | ~30 KB |
dotnet add package IronMapperusing IronMapper.Attributes;
[MapTo(typeof(UserDto))]
public class UserEntity
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
}
public class UserDto
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
}var entity = new UserEntity { Id = 1, Name = "Alice", Email = "alice@example.com" };
UserDto dto = entity.MapToUserDto(); // generated extension methodThat's it. No IMapper, no service registration, no startup overhead.
[MapTo(typeof(ProductDto))] // source → dest
[MapFrom(typeof(ProductEntity))] // dest ← source (equivalent)[MapProperty("FullName")]
public string Name { get; set; } = ""; // maps to FullName on destination[Ignore]
public string InternalSecret { get; set; } = "";public class PriceConverter : ITypeConverter<decimal, string>
{
public string Convert(decimal source) => source.ToString("F2");
}
[MapTo(typeof(InvoiceDto))]
public class Invoice
{
[MapConverter(typeof(PriceConverter))]
public decimal Total { get; set; }
}public class OrderProfile : MappingProfile
{
public OrderProfile()
{
CreateMap<OrderEntity, OrderDto>()
.ForMember(d => d.FullAddress, o => o.MapFrom(s => s.Street + ", " + s.City))
.ForMember(d => d.InternalId, o => o.Ignore());
}
}List<UserEntity> entities = ...;
List<UserDto> dtos = entities.MapToUserDtoList();
UserDto[] arr = entities.MapToUserDtoArray();entity.MapToUserDto(existingDto); // updates existing object without allocating a new one[MapTo(typeof(BlogDto))]
public class BlogEntity
{
public List<CommentEntity> Comments { get; set; } = new();
}
// generator emits: Comments = Enumerable.ToList(Enumerable.Select(source.Comments, x => x.MapToCommentDto()))// Program.cs
builder.Services.AddIronMapper(typeof(Program).Assembly);
// In a controller / service
public class OrdersController(IMapper mapper) : ControllerBase
{
public OrderDto Get(int id) => mapper.Map<OrderDto>(_repo.GetById(id));
}We've built a sample console app that showcases all IronMapper features using the real Open Library API (no API key required).
What the demo covers:
- Simple attribute-based mapping (
[MapTo]) - Fluent API with
MappingProfile - Collection mapping (
List<BookDoc>→List<BookSummary>) - Nested object mapping (Book + Author)
- Custom type converters (HTML bio cleaner)
- Conditional mapping (living authors only)
Run it:
git clone https://github.com/iron-mapper/IronMapper
cd IronMapper
dotnet run --project samples/IronMapper.Demo- Getting Started
- Configuration Reference
- Advanced Topics
- Migrating from AutoMapper
- Compiler Diagnostics
Contributions are welcome! Please:
- Fork the repo and create a feature branch (
feature/my-feature). - Make your changes with tests.
- Run
dotnet buildanddotnet test— both must be clean. - Open a pull request against
main.
Please follow the existing code style (see .editorconfig).
MIT © Iron Programmer School