SwiftMap 1.1.0

dotnet add package SwiftMap --version 1.1.0
                    
NuGet\Install-Package SwiftMap -Version 1.1.0
                    
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="SwiftMap" Version="1.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="SwiftMap" Version="1.1.0" />
                    
Directory.Packages.props
<PackageReference Include="SwiftMap" />
                    
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 SwiftMap --version 1.1.0
                    
#r "nuget: SwiftMap, 1.1.0"
                    
#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 SwiftMap@1.1.0
                    
#: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=SwiftMap&version=1.1.0
                    
Install as a Cake Addin
#tool nuget:?package=SwiftMap&version=1.1.0
                    
Install as a Cake Tool

<div align="right"><a href="README.pt-BR.md">πŸ‡§πŸ‡· PortuguΓͺs</a></div>

<div align="center">

SwiftMap

Zero-allocation, expression-tree-compiled object mapper for .NET

NuGet .NET License: MIT

Convention-based property mapping backed by compiled expression trees β€” zero reflection at call time. Source generator mode achieves manual-mapping parity at compile time (Mapperly-style).

</div>


Why SwiftMap?

  • Source generator mode β€” [Mapper] partial class emits mapping bodies at compile time; zero startup cost, zero delegate dispatch, zero reflection at call time β€” identical to hand-written code
  • Runtime expression-tree mode β€” mappings compile once to native delegates via FastExpressionCompiler; subsequent calls are allocation-free
  • Fluent, discoverable API β€” ForMember, Ignore, AfterMap, ReverseMap, NullSubstitute, Condition, Patch, and more
  • No heavy dependencies β€” single NuGet package; only depends on Microsoft.Extensions.DependencyInjection.Abstractions
  • Records & init-only properties β€” full support for C# 9+ records via primary constructor selection
  • First-class DI support β€” drop-in services.AddSwiftMap(...) with profile scanning

Installation

.NET CLI

dotnet add package SwiftMap

Package Manager Console

Install-Package SwiftMap

PackageReference

<PackageReference Include="SwiftMap" Version="1.0.0" />

Quick Start

using SwiftMap;

[Mapper]
public partial class AppMapper
{
    public partial PersonDto   Map(Person source);
    public partial OrderDto    Map(Order source);
    public partial ProductDto  Map(Product source);
}

// Usage β€” no DI, no startup cost, velocity = manual code
var mapper = new AppMapper();
var dto = mapper.Map(person);

The [Mapper] attribute triggers the included Roslyn source generator. It analyses your types at compile time and emits efficient object-initializer bodies β€” supporting flat objects, nested objects (null-safe is-pattern), collections (for-loop, no LINQ), records (primary constructor), enums, and nullable unwrapping.

Runtime Expression-Tree Mode

// 1. Create a mapper β€” once at startup or via DI
var mapper = Mapper.Create(cfg =>
    cfg.CreateMap<PersonSource, PersonDest>());

// 2. Map by source type inference
var dest = mapper.Map<PersonDest>(source);

// 3. Map with explicit type parameters (slightly faster β€” no GetType() call)
var dest = mapper.Map<PersonSource, PersonDest>(source);

// 4. Map into an existing instance
mapper.Map(source, existingDestination);

Configuration

Convention mapping β€” no config needed

Matching properties are wired up automatically by name (case-insensitive):

var mapper = Mapper.Create(_ => { });
var dto = mapper.Map<CustomerDto>(customer);

Fluent API

var mapper = Mapper.Create(cfg =>
    cfg.CreateMap<Customer, CustomerDto>(map =>
        map.ForMember(d => d.AddressCity,  opt => opt.MapFrom(s => s.Address!.City))
           .Ignore(d => d.InternalId)
           .ForMember(d => d.Name,         opt => opt.NullSubstitute("Unknown"))
           .ForMember(d => d.Score,        opt => opt.Condition(s => s.IsActive))
           .AfterMap((src, dest) => dest.Name = dest.Name.ToUpperInvariant())));

Reverse mapping

cfg.CreateMap<OrderDto, Order>()
   .ReverseMap(); // registers the inverse mapping automatically

Profiles

Organise large sets of mappings into cohesive units:

public class AppProfile : MapProfile
{
    public AppProfile()
    {
        CreateMap<Customer, CustomerDto>()
            .ForMember(d => d.AddressCity, opt => opt.MapFrom(s => s.Address!.City));

        CreateMap<Order, OrderDto>().ReverseMap();
    }
}

var mapper = Mapper.Create(cfg => cfg.AddProfile<AppProfile>());

Attribute-driven mapping

[MapTo(typeof(ProductDto))]
public class Product { ... }

public class TargetDto
{
    [MapProperty("FullName")]   // maps from a differently-named source property
    public string Name { get; set; }

    [IgnoreMap]                 // skipped during mapping
    public string Secret { get; set; }
}

Dependency Injection

// Inline configuration
services.AddSwiftMap(cfg => cfg.CreateMap<Order, OrderDto>());

// Scan assemblies for MapProfile subclasses and [MapTo]/[MapFrom] attributes
services.AddSwiftMap(typeof(Program).Assembly);

HTTP PATCH β€” Patch Semantics

Patch applies only the non-null fields from source onto an existing destination instance, leaving everything else untouched. Designed for HTTP PATCH endpoints where the client sends only the fields it wants to change.

// Only Name was sent β€” Age and Email are null (not provided by client)
var updateDto = new UpdateUserDto { Name = "JoΓ£o", Age = null };
var user = await dbContext.Users.FindAsync(id); // { Name = "Maria", Age = 30, Email = "m@m.com" }

mapper.Patch(updateDto, user);
// Result: { Name = "JoΓ£o", Age = 30, Email = "m@m.com" }
// Age and Email were preserved β€” they were null in the dto

Works automatically by convention β€” no CreateMap required. For advanced scenarios:

// Skip fields that match their default value (0, false, Guid.Empty, etc.)
mapper.Patch(dto, entity, cfg => cfg.AsPatch(PatchBehavior.SkipDefaultFields));

// Register patch behavior in a profile
public class AppProfile : MapProfile
{
    public AppProfile()
    {
        CreateMap<UpdateUserDto, User>().AsPatch();
    }
}
Source field type Behavior
string / reference type Skipped if null
Nullable<T> (int?, bool?, …) Skipped if !HasValue
Non-nullable value type (int, bool, …) Always applied (cannot be null)

Benchmarks

Benchmarked against AutoMapper 13.0.1 and Mapster 7.4.0 on .NET 9.

BenchmarkDotNet v0.15.8  Β·  Windows 11  Β·  AMD Ryzen 5 3600 3.60GHz (6C/12T)
.NET SDK 9.0.312  Β·  .NET 9.0.14  Β·  X64 RyuJIT x86-64-v3
ShortRun: 3 warmups + 7 iterations

Simple flat object (7 properties)

Method Mean Error StdDev Ratio Allocated
Manual 15.19 ns 4.863 ns 2.159 ns baseline 64 B
SwiftGenerated 15.27 ns 5.110 ns 2.269 ns 1.03Γ— 64 B
Mapster 30.37 ns 4.921 ns 2.185 ns 2.04Γ— slower 64 B
SwiftMap (runtime) 39.75 ns 8.234 ns 3.656 ns 2.67Γ— slower 64 B
AutoMapper 89.95 ns 17.732 ns 7.873 ns 6.04Γ— slower 64 B

Nested object (parent + child)

Method Mean Error StdDev Ratio Allocated
Manual 22.18 ns 6.680 ns 2.966 ns baseline 104 B
SwiftGenerated 22.97 ns 6.421 ns 2.851 ns 1.05Γ— 104 B
Mapster 40.52 ns 8.772 ns 3.895 ns 1.86Γ— slower 104 B
SwiftMap (runtime) 48.76 ns 13.197 ns 5.859 ns 2.23Γ— slower 104 B
AutoMapper 102.62 ns 18.835 ns 8.363 ns 4.70Γ— slower 104 B

Collection mapping

Method Count Mean Error StdDev Ratio Allocated
Manual 100 1.425 Β΅s 0.534 Β΅s 0.237 Β΅s baseline 7.05 KB
SwiftGenerated 100 1.519 Β΅s 0.573 Β΅s 0.254 Β΅s 1.09Γ— 7.05 KB
Mapster 100 2.637 Β΅s 0.542 Β΅s 0.241 Β΅s 1.90Γ— slower 7.05 KB
SwiftMap (runtime) 100 4.372 Β΅s 0.968 Β΅s 0.430 Β΅s 3.14Γ— slower 7.05 KB
AutoMapper 100 8.486 Β΅s 3.128 Β΅s 1.389 Β΅s 6.10Γ— slower 7.05 KB
Manual 1000 14.202 Β΅s 5.038 Β΅s 2.237 Β΅s baseline 70.34 KB
SwiftGenerated 1000 15.540 Β΅s 4.086 Β΅s 1.814 Β΅s 1.12Γ— 70.34 KB
Mapster 1000 30.888 Β΅s 7.347 Β΅s 3.262 Β΅s 2.23Γ— slower 70.34 KB
SwiftMap (runtime) 1000 43.908 Β΅s 13.642 Β΅s 6.057 Β΅s 3.17Γ— slower 70.34 KB
AutoMapper 1000 91.405 Β΅s 14.038 Β΅s 6.233 Β΅s 6.59Γ— slower 70.34 KB

Record (primary constructor)

Method Mean Error StdDev Ratio Allocated
Manual 11.27 ns 4.766 ns 2.116 ns baseline 48 B
SwiftGenerated 12.89 ns 4.260 ns 1.891 ns 1.18Γ— 48 B
Mapster 30.10 ns 6.656 ns 2.955 ns 2.74Γ— slower 48 B
SwiftMap (runtime) 37.35 ns 7.100 ns 3.153 ns 3.40Γ— slower 48 B
AutoMapper 86.29 ns 16.937 ns 7.520 ns 7.86Γ— slower 48 B

At a glance

Scenario SwiftGenerated vs Manual SwiftGenerated vs Mapster SwiftGenerated vs AutoMapper
Simple object β‰ˆ parity (1.03Γ—) 2.0Γ— faster 5.9Γ— faster
Nested object β‰ˆ parity (1.05Γ—) 1.8Γ— faster 4.5Γ— faster
Collection Γ—1000 β‰ˆ parity (1.12Γ—) 2.0Γ— faster 5.9Γ— faster
Record β‰ˆ parity (1.18Γ—) 2.3Γ— faster 6.7Γ— faster

All measurements: same allocated memory as manual code (no overhead).


Project Structure

src/
β”œβ”€β”€ SwiftMap/                              # Runtime expression-tree mapper
β”‚   β”œβ”€β”€ Mapper.cs                          # Entry point β€” Mapper.Create(...)
β”‚   β”œβ”€β”€ IMapper.cs                         # Public interface
β”‚   β”œβ”€β”€ MapperConfig.cs                    # Configuration + compiled delegate cache
β”‚   β”œβ”€β”€ TypeMapConfig.cs                   # Fluent API: ForMember, Ignore, AfterMap...
β”‚   β”œβ”€β”€ MapProfile.cs                      # Base class for profiles
β”‚   β”œβ”€β”€ Attributes/
β”‚   β”‚   └── MapToAttribute.cs              # [MapTo], [MapFrom], [IgnoreMap], [MapProperty], [Mapper]
β”‚   β”œβ”€β”€ Extensions/
β”‚   β”‚   └── ServiceCollectionExtensions.cs # AddSwiftMap(...)
β”‚   └── Internal/
β”‚       β”œβ”€β”€ MappingCompiler.cs             # Expression tree compiler (core engine)
β”‚       └── TypePair.cs                    # (Source, Destination) dictionary key
└── SwiftMap.SourceGenerator/              # Roslyn IIncrementalGenerator
    β”œβ”€β”€ MapperGenerator.cs                 # [Generator] entry point
    β”œβ”€β”€ Models/                            # MapperClassModel, MappingMethodModel, MappedPropertyModel
    β”œβ”€β”€ Pipeline/
    β”‚   └── MapperModelExtractor.cs        # Semantic analysis
    └── Emit/
        β”œβ”€β”€ MappingBodyEmitter.cs          # Code generation
        └── SourceWriter.cs                # Indented string builder

Roadmap

  • FastExpressionCompiler integration β€” CompileFast() replaces Expression.Compile() for faster delegate creation
  • Source generator mode β€” [Mapper] partial class emits mapping bodies at compile time; reaches manual-mapping parity
  • Patch semantics β€” mapper.Patch(dto, entity) applies only non-null fields; built-in support for HTTP PATCH endpoints
  • Async mapping β€” MapAsync<TDest>(source) for pipelines that need async value resolution
  • IQueryable projection β€” ProjectTo<TDest>() for ORM query projection
  • NuGet release β€” SwiftMap is available on nuget.org/packages/SwiftMap

Contributing

Contributions are welcome! See CONTRIBUTING.md for full guidelines.

Quick steps:

  1. Fork and create a feature branch (git checkout -b feature/my-feature)
  2. Add or update tests to cover your change
  3. Run the benchmarks to verify no performance regression:
    dotnet run -c Release --project benchmarks/SwiftMap.Benchmarks
    
  4. Open a pull request β€” for significant changes, open an issue first to discuss the approach

License

SwiftMap is released under the MIT License.

Product Compatible and additional computed target framework versions.
.NET net9.0 is compatible.  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 was computed.  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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.1.0 108 3/19/2026
1.0.0 103 3/15/2026