Marai Light Mapper

NuGet License

Version: 1.0.2
Target Framework: net10.0+
NuGet Package ID: Marai.LightMapper


Introduction

Marai.LightMapper is a lightweight object mapper for .NET that maps between source and destination types using a marker-interface convention. It supports both automatic property mapping (by name and type) and custom mapping logic defined directly on your models.

Major capabilities:

  • Convention-based auto-mapping by matching property names (case-insensitive)
  • Custom mapping logic via IMapFrom<TSource> and IMapTo<TDestination> interfaces
  • Automatic type conversions: numeric types, Guid, DateTime, bool, enums, and string variants
  • Assembly scanning to discover and register all mappings at startup
  • Microsoft Dependency Injection integration
  • Safe for concurrent use — mappings are stored in a ConcurrentDictionary

Table of Contents


Installation

1 dotnet add package Marai.LightMapper
1 Install-Package Marai.LightMapper

Getting Started

Register with Dependency Injection

Call AddMaraiLightMapper in your Program.cs, passing the assemblies that contain your mapping types.

1 2 3 using Marai.LightMapper; builder.Services.AddMaraiLightMapper(typeof(Program).Assembly);

You can call AddMaraiLightMapper multiple times with different assemblies. Subsequent calls reuse the same MappingRegistry and accumulate mappings.

1 2 builder.Services.AddMaraiLightMapper(typeof(Program).Assembly); builder.Services.AddMaraiLightMapper(typeof(InfrastructureAssemblyMarker).Assembly);

AddMaraiLightMapper registers:

  • MappingRegistry (singleton) — stores all registered mapping functions
  • IMapper (singleton) — the mapping entry point

Use without DI

1 2 3 var registry = new MappingRegistry(); AssemblyScanner.ScanAndRegister(registry, typeof(MyDto).Assembly); IMapper mapper = new Mapper(registry);

Mapping Conventions

Auto-Mapping

When a type implements IMapFrom<TSource> or IMapTo<TDestination> without overriding the default interface method, LightMapper automatically maps properties by matching names (case-insensitive). Source properties with no matching destination property are ignored. Destination properties with no matching source property are left at their default value.

1 2 3 4 5 public class UserDto : IMapFrom<UserEntity> { public Guid Id { get; set; } public string Name { get; set; } }

Custom Mapping with IMapFrom

Override MapFrom on the destination type to control exactly how the source is mapped.

1 2 3 4 5 6 7 8 9 10 11 public class UserDto : IMapFrom<UserEntity> { public Guid Id { get; set; } public string FullName { get; set; } public void MapFrom(UserEntity source) { Id = source.Id; FullName = $”{source.FirstName} {source.LastName}”; } }
The MapFrom override must be declared directly on the class (DeclaredOnly). Interface default implementations are not treated as custom logic.

Custom Mapping with IMapTo

Implement IMapTo<TDestination> on the source type to push mapping logic onto the source.

1 2 3 4 5 6 7 8 9 10 11 12 13 public class UserEntity : IMapTo<UserDto> { public Guid Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public void MapTo(UserDto destination) { destination.Id = Id; destination.FullName = $”{FirstName} {LastName}”; } }

Using the Mapper

Inject IMapper wherever you need to convert objects.

1 2 3 4 5 6 7 public class UserService(IMapper mapper) { public UserDto GetUser(UserEntity entity) { return mapper.Map<UserDto>(entity); } }

To map to a type known only at runtime:

1 object dto = mapper.Map(entity, typeof(UserDto));
Mapping null returns null without throwing.

Supported Type Conversions

When auto-mapping, LightMapper converts mismatched property types using the following rules:

Source Type Destination Type Behavior
string Guid Guid.TryParse — skips property if invalid
Guid string ToString()
string Numeric types Convert.ChangeType — skips if blank
Numeric Numeric Convert.ChangeType
string Enum Enum.Parse (case-insensitive) — skips if blank
Numeric Enum Enum.ToObject
Enum string ToString()
string DateTime DateTime.TryParse — skips if blank
DateTime string ISO 8601 round-trip format ("O")
string bool bool.TryParse — skips if blank
Any Nullable variants Unwraps nullable and applies matching rule

Properties that cannot be converted are silently skipped.


Exceptions Reference

Exception Thrown When Properties
MappingNotFoundException IMapper.Map is called for a source/destination pair with no registered mapping None beyond the message
1 2 3 4 5 6 7 8 try { var dto = mapper.Map<UserDto>(entity); } catch (MappingNotFoundException ex) { // No mapping registered from UserEntity to UserDto Console.WriteLine(ex.Message); }
A mapping is only registered when the source or destination type implements IMapFrom<> or IMapTo<> and the assembly containing that type was passed to AddMaraiLightMapper.

Full API Reference

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 // Abstractions interface IMapper { TDestination Map<TDestination>(object source); // Map source to TDestination object Map(object source, Type destinationType); // Map source to runtime type } interface IMapFrom<TSource> { void MapFrom(TSource source); // Override for custom mapping logic (optional) } interface IMapTo<TDestination> { void MapTo(TDestination destination); // Override for custom mapping logic (optional) } // Registry class MappingRegistry { void Register(Type source, Type destination, Func<object, object> map); // Register a mapping function bool TryGet(Type source, Type destination, out Func<object, object>? map); // Look up a mapping function } // DI Registration static IServiceCollection AddMaraiLightMapper(this IServiceCollection services, params Assembly[] assemblies); // Exceptions sealed class MappingNotFoundException : Exception { }

License

This project is licensed under the Marai Proprietary Software License Agreement.

Free for personal and commercial use.

For the complete license agreement and terms, please visit: https://marai.dev/proprietary-software-license-agreement