YounMapper 1.1.2
dotnet add package YounMapper --version 1.1.2
NuGet\Install-Package YounMapper -Version 1.1.2
<PackageReference Include="YounMapper" Version="1.1.2" />
<PackageVersion Include="YounMapper" Version="1.1.2" />
<PackageReference Include="YounMapper" />
paket add YounMapper --version 1.1.2
#r "nuget: YounMapper, 1.1.2"
#:package YounMapper@1.1.2
#addin nuget:?package=YounMapper&version=1.1.2
#tool nuget:?package=YounMapper&version=1.1.2
YounMapper
A small, fast, convention-based object-to-object mapper for .NET — profiles, property renames, flattening, custom type converters, and collection mapping. No dependency injection container required.
The mapping semantics are defined in a language-neutral contract (SEMANTICS.md) so the library can be ported to TypeScript, Python, and other languages with identical behavior.
Quickstart
Install the package:
dotnet add package YounMapper
Three lines to a mapped object:
using YounMapper;
var mapper = new MapperConfiguration(cfg => cfg.AddProfile<MyProfile>())
.CreateMapper();
var dto = mapper.Map<OrderDto>(order); // convention mapping, zero config
With a profile for the non-conventional parts:
public class MyProfile : Profile
{
public MyProfile()
{
CreateMap<Order, OrderDto>()
.ForMember(dest => dest.CustomerName,
opt => opt.MapFrom(src => src.Customer.Name));
}
}
No container is required — MapperConfiguration and IMapper work standalone.
Mapper instances are immutable and reusable. When resolvers use scoped services,
create a mapper within each service scope rather than capturing those services
in an application-wide singleton.
Features
| Feature | How | Example |
|---|---|---|
| Convention-based mapping | Matching names (case-insensitive) with assignable types map automatically | src.Name → dest.Name |
| Mapping profiles | Profile + CreateMap<T1, T2>(), last registration wins |
cfg.AddProfile<MyProfile>() |
| Property renames | ForMember(...).MapFrom(...) |
dest.CustomerName ← src.Customer.Name |
| Ignore | Preserve initialized or existing values | opt.Ignore() |
| Reverse maps | Reverse property paths and flattening | .ReverseMap() |
| Custom construction | Create a destination before mapping members | .ConstructUsing(src => new Dto(src.Id)) |
| Member resolvers | Typed resolver or supplied instance | opt.MapFrom<AssetPolicyResolver>() |
| Existing destinations | Populate an existing object | mapper.Map(source, destination) |
| Runtime types | Map with resolved type descriptors | mapper.Map(source, typeof(A), typeof(B)) |
| Flattening | Automatic, null-safe, greedy prefix decomposition | src.Customer.Address.City → dest.CustomerAddressCity |
| Custom type converters | ITypeConverter<TSource, TDestination> or a Func delegate |
CreateMap<A, B>().ConvertUsing(a => new B(...)) |
| Collections | Element-wise mapping for IEnumerable<T>/List<T>/T[] |
List<Order> → List<OrderDto> |
| Null handling | Null source → null destination; null collections → null (or empty via one global switch) | cfg.MapNullCollectionsToEmpty() |
AutoMapper-style APIs (1.1.x)
The familiar syntax below is supported with zero runtime package dependencies.
YounMapper is not a full or binary-compatible replacement for AutoMapper: use
using YounMapper; for mapping configuration. Resolvers may implement YounMapper's
interface or expose a compatible public Resolve method (since 1.1.1).
using YounMapper;
IMapper mapper = new MapperConfiguration(cfg => cfg.AddProfile<AssetProfile>()).CreateMapper();
var asset = new Asset { Id = 7, Name = "Router", IsRestricted = true };
var dto = mapper.Map<AssetDto>(asset);
var existing = new AssetDto(7) { AuditNote = "Keep this" };
mapper.Map(asset, existing);
var restored = mapper.Map<Asset>(dto);
var runtimeDto = (AssetDto)mapper.Map(asset, typeof(Asset), typeof(AssetDto))!;
public sealed class AssetProfile : Profile
{
public AssetProfile()
{
CreateMap<Asset, AssetDto>()
.ConstructUsing(src => new AssetDto(src.Id))
.ForMember(dest => dest.DisplayName, opt => opt.MapFrom(src => src.Name))
.ForMember(dest => dest.AuditNote, opt => opt.Ignore())
.ForMember(dest => dest.Policy, opt => opt.MapFrom<AssetPolicyResolver>())
.ReverseMap();
}
}
public sealed class AssetPolicyResolver : IValueResolver<Asset, AssetDto, string>
{
public string Resolve(Asset source, AssetDto destination, string destMember, ResolutionContext context)
=> source.IsRestricted ? "Restricted" : "Standard";
}
public sealed class Asset
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsRestricted { get; set; }
}
public sealed class AssetDto
{
public AssetDto(int id) => Id = id;
public int Id { get; }
public string? DisplayName { get; set; }
public string? AuditNote { get; set; }
public string Policy { get; set; } = "";
}
Ignore()preserves initialized/existing values and never reads the source member.ConstructUsing()runs before member mapping; it is skipped for existing destinations.MapFrom<TResolver>()validates the signature during registration and constructs one reusable resolver per member rule when creating each mapper. From 1.1.2, constructor dependencies are supported throughcfg.ConstructServicesUsing(...); without a factory, the resolver needs a public parameterless constructor. Supplied instances bypass the factory. YounMapper'scontext.Mapperallows nested mapping; resolver state must be thread-safe.- From 1.1.1, existing resolvers do not have to implement
YounMapper.IValueResolver. Keepopt.MapFrom<AssetPolicyResolver>(): a public instanceResolve(source, destination, destMember)orResolve(source, destination, destMember, context)is bound once. Parameter/result types must match the mapping; ambiguous or unsupported signatures fail during configuration. Native YounMapper resolvers take precedence. Important: a foreign context such asAutoMapper.ResolutionContextreceives null. Existing AutoMapper resolvers work only when they do not use that context; itsMapper,Items,State, and service-resolution features are not emulated. Keep AutoMapper referenced in your application while its types remain in your resolver declarations; YounMapper itself still has no runtime package dependencies. ReverseMap()reverses public property paths, renames, and implicit flattening (unflattening creates missing public nested objects). Factory/resolver/converter logic, Ignore, and computed expressions are directional, not inverted. It is not a lossless round trip; configure any additional reverse rules after the call.Map(source, existing)returns the populated instance. Nested members may be replaced, not merged. A null source leaves it unchanged; a null destination creates a new one. Whole converters and top-level collection maps may return replacements.- Runtime-type forms are
Map(source, sourceType, destinationType)andMap(source, destination, sourceType, destinationType).
Resolvers with constructor dependencies (1.1.2)
Keep .ForMember(d => d.UploadedBy, o => o.MapFrom<EmployeeEmailResolver>()).
Configure ConstructServicesUsing to construct the resolver with your service.
For an ASP.NET Core application with IInfoMappingService already registered:
using Microsoft.Extensions.DependencyInjection;
builder.Services.AddScoped<EmployeeEmailResolver>();
builder.Services.AddScoped<YounMapper.IMapper>(sp =>
new YounMapper.MapperConfiguration(cfg =>
{
cfg.AddProfile<MappingProfile>();
cfg.ConstructServicesUsing(sp.GetRequiredService);
}).CreateMapper());
Register every resolver used by MapFrom<TResolver>() with the service provider
when using GetRequiredService, including parameterless resolvers. Keep your
existing IInfoMappingService implementation/registration. Do not build another
service provider or register a singleton mapper that captures scoped dependencies.
Resolvers and their dependencies are owned/disposed by the application's scope,
not YounMapper. This callback also supports compatible legacy resolvers; foreign
context parameters still receive null.
Without a container, supply your own factory, for example
cfg.ConstructServicesUsing(type => type == typeof(EmployeeEmailResolver) ? new EmployeeEmailResolver(infoMappingService) : throw new InvalidOperationException($"Unregistered resolver: {type}"));
or pass an already-created resolver with opt.MapFrom(instance).
Query projection, DI integration packages, validation APIs, general ForPath
configuration, and automatic reversal of arbitrary business logic are not implemented.
Design guarantees
- Compiled, cached plans — reflection runs once per type pair; the hot path is a compiled delegate with zero reflection (see benchmarks).
- Public members only — private/internal state is never read or written.
- No code generated from untrusted data — trusted expression trees are compiled and cached. Nothing is parsed from strings or input data. Callbacks and property accessors are application code, not sandboxed. See SECURITY.md.
- Fail fast — invalid member rules and type mismatches throw at mapper creation, not at map time.
Benchmarks
dotnet run -c Release --project benchmarks/YounMapper.Benchmarks
A convention-only map of a flat 10-property pair completes in well under 1 µs per call after warmup.
Roadmap / ports
v1.x— C#/.NET only; the feature set defined in SEMANTICS.md.- TypeScript — planned port following the same semantics contract.
- Python — planned port following the same semantics contract.
- Later — query projection, validation, DI-container integration packages. Explicitly not in v1.
Contributing
See SECURITY.md for the security review checklist and RELEASING.md for the release process. Tests and coverage:
dotnet test
node tools/coverage.js --min-line 90 --min-branch 90 # after running tests with coverlet
License
MIT — see LICENSE.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | 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 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. |
-
net8.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.