Mapture 1.1.1

dotnet add package Mapture --version 1.1.1
                    
NuGet\Install-Package Mapture -Version 1.1.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Mapture" Version="1.1.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Mapture" Version="1.1.1" />
                    
Directory.Packages.props
<PackageReference Include="Mapture" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Mapture --version 1.1.1
                    
#r "nuget: Mapture, 1.1.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Mapture@1.1.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Mapture&version=1.1.1
                    
Install as a Cake Addin
#tool nuget:?package=Mapture&version=1.1.1
                    
Install as a Cake Tool

Mapture

The fastest object mapper for .NET — with safety features no other mapper has.

Build NuGet License: MIT

Disclaimer: This project is an independent implementation and is not affiliated with AutoMapper, Mapster, PanoramicData.Mapper, or any other mapping library.


The Story

You've been here before. Your API maps User to UserDto ten thousand times per second, and one day someone adds a Parent property that points back to itself. The stack overflows. Production goes down. Nobody knows why.

Or maybe it's subtler — a mapping library that phones home with telemetry you never asked for. Or one that only supports .NET 8+, leaving your legacy services stranded.

Mapture was built for teams who are tired of compromise. It's the only .NET mapper that is simultaneously:

  • Fastest — benchmarked faster than Mapster, AutoMapper, and PanoramicData.Mapper
  • Safest — cycle detection and max-depth enforcement catch infinite recursion before it happens
  • Broadest — runs on .NET Framework 4.8, .NET Standard 2.0, .NET 8, and .NET 10
  • Cleanest — zero telemetry, zero surprises, familiar API, 30-minute migration from AutoMapper

Benchmark Results

Measured with BenchmarkDotNet on .NET 10.0, mapping a simple object with one nested child. All libraries configured with equivalent mappings, run on the same hardware:

Rank Method Mean vs Manual Allocated
🥇 Manual Mapping ~17 ns baseline 96 B
🥈 Mapture ~25 ns 1.5x 96 B
🥉 Mapster ~27 ns 1.6x 96 B
4 AutoMapper ~68 ns 4.0x 96 B
5 PanoramicData.Mapper ~283 ns 16.9x 272 B

How? Mapture compiles expression trees at configuration time and caches the compiled delegates per type pair. Acyclic type graphs get a zero-overhead fast path that skips all cycle/depth tracking. The result: your mapping function runs almost as fast as code you'd write by hand.


Quick Start

1. Install

dotnet add package Mapture
dotnet add package Mapture.Extensions.DependencyInjection

2. Define a Profile

using Mapture;

public class UserProfile : Profile
{
    public UserProfile()
    {
        CreateMap<User, UserDto>()
            .ForMember(d => d.FullName, opt => opt.MapFrom(s => s.FirstName + " " + s.LastName))
            .Ignore(d => d.InternalId);
    }
}

3. Register with DI

using Mapture.Extensions.DependencyInjection;

builder.Services.AddMapture(typeof(Program).Assembly);

4. Map Objects

public class UsersController : ControllerBase
{
    private readonly IMapper _mapper;

    public UsersController(IMapper mapper) => _mapper = mapper;

    [HttpGet]
    public IActionResult Get()
    {
        var users = _userService.GetAll();
        return Ok(users.Select(u => _mapper.Map<User, UserDto>(u)));
    }
}

That's it. Convention-based matching handles properties with the same name. Custom mappings, ignores, and reverse maps are all one fluent call away.


Feature Comparison

Feature Mapture AutoMapper Mapster PanoramicData.Mapper
Performance rank 🥈 4th 🥉 5th
Convention-based mapping
Profile system
ForMember / Ignore
ReverseMap
Nested object mapping
Collection mapping
IEnumerable<TSrc>List<TDest> ⚠️
Non-nullable → nullable coercion (intint?, boolbool?, etc.) ⚠️
Numeric widening / narrowing (intlong, longint, etc.) ⚠️
In-place update (Map(src, dest) with mismatched nullability) ⚠️
Enum ↔ string / numeric coercion ⚠️
Cycle detection
Max depth enforcement
ConvertUsing
BeforeMap / AfterMap
ConstructUsing
Condition
MapFrom (Expression)
MapFrom (string source-member name)
ResolveUsing (Func<TSource, TMember>) ⚠️
ResolveUsing with destination access ⚠️
NullSubstitute
UseValue (constant)
Configuration validation
DI integration
No forced telemetry
.NET Framework 4.8
.NET Standard 2.0
.NET 8 / .NET 10 ⚠️ .NET 10 only

Safety Features

Cycle Detection

Objects that reference themselves (directly or indirectly) are the #1 cause of StackOverflowException in mapping libraries. Mapture detects cycles at runtime and breaks them safely:

var node = new Node { Id = 1 };
node.Parent = node; // Circular reference!

var dto = mapper.Map<Node, NodeDto>(node);
// dto.Parent is null — cycle safely broken, no stack overflow

Max Depth Enforcement

Even without cycles, deeply nested object graphs can exhaust the stack. Mapture caps recursion at a configurable depth:

services.AddMapture(typeof(Program).Assembly, options =>
{
    options.MaxDepth = 5; // Stop mapping beyond 5 levels deep
});

Smart Cycle Analysis

Mapture analyzes your type graph at configuration time. Types that cannot have cycles (no self-referencing paths) get a zero-overhead fast path — no HashSet, no depth counter, no per-call allocation. You get safety where you need it and raw speed everywhere else.

Configuration Validation

Catch unmapped properties at startup — not at 3am in production:

var config = new MapperConfiguration(cfg =>
{
    cfg.CreateMap<User, UserDto>();
});

config.AssertConfigurationIsValid();
// Throws MaptureException if any destination property has no source and isn't ignored

Fluent API

ForMember — Custom Property Mapping

// Strongly-typed expression (preferred, optimized into the compiled delegate)
CreateMap<User, UserDto>()
    .ForMember(d => d.FullName,
        opt => opt.MapFrom(s => s.First + " " + s.Last));

// By source-member name (matches AutoMapper's string overload)
CreateMap<User, UserDto>()
    .ForMember(d => d.DisplayName, opt => opt.MapFrom("FirstName"));

ResolveUsing — Runtime Resolver Delegate

Use when a closure or runtime state prevents authoring an Expression:

CreateMap<User, UserDto>()
    .ForMember(d => d.Token, opt => opt.ResolveUsing(s => _tokenService.For(s.Id)));

// Or with access to the current destination value
CreateMap<User, UserDto>()
    .ForMember(d => d.AuditTrail, opt => opt.ResolveUsing((s, d) => d.AuditTrail + ";" + s.LastLogin));

NullSubstitute — Default for Null Source Members

CreateMap<User, UserDto>()
    .ForMember(d => d.NickName, opt =>
    {
        opt.MapFrom(s => s.NickName);
        opt.NullSubstitute("(anonymous)");
    });

Ignore — Skip a Property

CreateMap<User, UserDto>()
    .Ignore(d => d.InternalSecret);

ReverseMap — Bidirectional Mapping

CreateMap<User, UserDto>().ReverseMap();
// Now both User→UserDto and UserDto→User work

ConvertUsing — Full Custom Conversion

CreateMap<User, UserDto>()
    .ConvertUsing(src => new UserDto
    {
        Id = src.Id,
        Name = src.Name.ToUpperInvariant()
    });

BeforeMap / AfterMap — Pre/Post Processing

CreateMap<User, UserDto>()
    .AfterMap((src, dest) => dest.Name = dest.Name.Trim());

ConstructUsing — Custom Instantiation

CreateMap<User, UserDto>()
    .ConstructUsing(src => new UserDto { Id = src.Id * 10 });

Condition — Conditional Mapping

CreateMap<User, UserDto>()
    .ForMember(d => d.Age, opt =>
    {
        opt.MapFrom(s => s.Age);
        opt.Condition(s => s.Age > 0);
    });

Automatic Type Coercion

No more ForMember boilerplate just because the destination type is wider:

CreateMap<Order, OrderDto>(); // That's it.

// Source -> Dest property type coercion handled automatically:
//   int      -> int? / long / long? / decimal / decimal? / double / double?
//   long     -> int / int? (with overflow check)
//   bool     -> bool?
//   DateTime -> DateTime? / DateTimeOffset?
//   decimal  -> decimal?
//   string   -> Enum   (Enum.Parse)
//   int      -> Enum   (cast)
//   Enum     -> string / int

Collection Projection from IEnumerable<T>

If CreateMap<Src, Dest>() is registered, Mapture automatically projects any IEnumerable<Src> into List<Dest>, Dest[], IList<Dest>, ICollection<Dest>, or IEnumerable<Dest> — no extra .ToList() calls required:

IEnumerable<User> users = repo.QueryUsers();
List<UserDto> dtos = mapper.Map<IEnumerable<User>, List<UserDto>>(users);

In-place Update Mapping

Use the two-parameter overload to update an existing destination instance — including when source/destination types differ in nullability:

var dto = new OrderDto();
mapper.Map(order, dto); // Coerces int->int?, bool->bool?, etc. on the fly

UseValue — Constant Value

CreateMap<User, UserDto>()
    .ForMember(d => d.Source, opt => opt.UseValue("API"));

Migration from AutoMapper

Mapture uses the same API patterns. Most migrations take under 30 minutes:

Step 1 — Replace NuGet Packages

dotnet remove package AutoMapper
dotnet remove package AutoMapper.Extensions.Microsoft.DependencyInjection
dotnet add package Mapture
dotnet add package Mapture.Extensions.DependencyInjection

Step 2 — Find & Replace Namespaces

Find Replace
using AutoMapper; using Mapture;
using AutoMapper.Extensions.Microsoft.DependencyInjection; using Mapture.Extensions.DependencyInjection;

Step 3 — Replace DI Registration

// Before
services.AddAutoMapper(typeof(Startup));

// After
services.AddMapture(typeof(Startup));

Step 4 — Run Tests

Most mappings work identically. The same Profile, CreateMap, ForMember, Ignore, and ReverseMap patterns are supported.


Configuration Options

services.AddMapture(typeof(Program).Assembly, options =>
{
    options.CompatibilityMode = true;    // Extra AutoMapper compat behaviors
    options.MaxDepth = 10;               // Prevent infinite recursion (default: 10)
    options.EnableCycleDetection = true;  // Detect circular references (default: true)
    options.EnableDebugTracing = false;   // Debug logging (default: false)
});

Architecture

Mapture.slnx
├── src/
│   ├── Mapture.Core                              # Core mapping engine
│   ├── Mapture.Compatibility                     # AutoMapper compatibility layer
│   ├── Mapture.Extensions.DependencyInjection    # DI registration extensions
│   └── Mapture.Benchmarks                        # BenchmarkDotNet performance tests
├── tests/
│   ├── Mapture.Tests                             # 37 unit tests × 3 frameworks = 111
│   └── Mapture.CompatibilityTests                # 3 compat tests × 3 frameworks = 9
├── enterprise/
│   └── Mapture.Enterprise                        # Metrics & audit (optional)
├── samples/
│   └── Mapture.Sample.Api                        # Sample ASP.NET Core Web API
└── docs/
    └── index.html                                # Full documentation site

How the Engine Works

  1. Configuration time — You define mappings via Profiles. Mapture builds a type map dictionary.
  2. First map call — Mapture compiles an expression tree for the type pair into a native delegate. For acyclic types, this delegate is pure property assignment — no reflection, no dictionary lookups, no allocations beyond the destination object.
  3. Subsequent calls — The compiled delegate is cached in a thread-static slot. The hot path is: null check → read cached delegate → call. That's it.

For cyclic types (detected at configuration time), Mapture wraps the delegate with cycle detection and depth tracking. You never need to think about it — the engine picks the right path automatically.


Enterprise Extension

Optional enterprise features available in Mapture.Enterprise:

  • Mapping metrics — execution counts and latency tracking
  • Audit trail — field-level lineage tracking
  • No forced telemetry — opt-in only, you control what's collected

License

MIT License. See LICENSE for details.

Disclaimer: This project is an independent implementation and is not affiliated with AutoMapper, Mapster, or any other object mapping library. Common API patterns (IMapper, Profile, CreateMap) are standard industry conventions used for migration compatibility.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 is compatible.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .NETFramework 4.8

    • No dependencies.
  • .NETStandard 2.0

    • No dependencies.
  • net10.0

    • No dependencies.
  • net8.0

    • No dependencies.

NuGet packages (4)

Showing the top 4 NuGet packages that depend on Mapture:

Package Downloads
Sparkdo.AutoMapper

Sparkdo Mapture 集成库,提供基于 Mapture 的对象映射功能实现

Mapture.Extensions.DependencyInjection

Dependency injection extensions for Mapture. Register IMapper and auto-discover Profiles with a single call to AddMapture().

Mapture.Compatibility

AutoMapper compatibility layer for Mapture. Enables easier migration from AutoMapper with compatible behaviors.

Fluxorq

High-performance request dispatching and pipeline framework for .NET, with first-class object mapping powered by Mapture. An original implementation inspired by mediator and middleware patterns.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.1.1 124 5/5/2026
1.1.0 176 4/30/2026
1.0.0 225 3/31/2026