-
Notifications
You must be signed in to change notification settings - Fork 0
Home
- Introduction
- Getting Started
- Basic Usage
- Advanced Features
- Mapping Modules
- Dependency Injection
- Performance Considerations
- API Reference
- Best Practices
- Troubleshooting
TurboMapper is a lightweight, high-performance object mapper for .NET that provides both shallow and deep mapping capabilities. It serves as a free alternative to AutoMapper with a simple, intuitive API.
- Automatic Property Mapping: Maps properties by name automatically
- Custom Property Mapping: Define explicit mappings between different property names
- Nested Object Support: Handles deep object hierarchies seamlessly
- Type Conversion: Automatic conversion between compatible types
- Mapping Modules: Organize mappings in reusable modules
- Dependency Injection: First-class support for Microsoft.Extensions.DependencyInjection
- Thread-Safe: Safe for concurrent operations
- Multi-Framework Support: Compatible with .NET Framework 4.6.2, .NET Standard 2.0/2.1, and .NET 9.0
dotnet add package TurboMapper
using TurboMapper;
using Microsoft.Extensions.DependencyInjection;
// Setup
var services = new ServiceCollection();
services.AddTurboMapper();
var serviceProvider = services.BuildServiceProvider();
var mapper = serviceProvider.GetService<IMapper>();
// Define models
public class Source
{
public string Name { get; set; }
public int Age { get; set; }
}
public class Target
{
public string Name { get; set; }
public int Age { get; set; }
}
// Map objects
var source = new Source { Name = "John Doe", Age = 30 };
var target = mapper.Map<Source, Target>(source);
TurboMapper automatically maps properties with matching names:
public class UserDTO
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
}
public class UserEntity
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
}
var dto = new UserDTO
{
FirstName = "Jane",
LastName = "Smith",
Age = 28
};
var entity = mapper.Map<UserDTO, UserEntity>(dto);
// entity.FirstName = "Jane"
// entity.LastName = "Smith"
// entity.Age = 28
TurboMapper automatically handles nested objects:
public class Address
{
public string Street { get; set; }
public string City { get; set; }
public string ZipCode { get; set; }
}
public class Person
{
public string Name { get; set; }
public Address Address { get; set; }
}
var source = new Person
{
Name = "John",
Address = new Address
{
Street = "123 Main St",
City = "Boston",
ZipCode = "02101"
}
};
var target = mapper.Map<Person, Person>(source);
// All nested properties are mapped automatically
TurboMapper handles common type conversions:
public class Source
{
public int Age { get; set; }
public string Status { get; set; }
}
public class Target
{
public string Age { get; set; } // int to string
public StatusEnum Status { get; set; } // string to enum
}
public enum StatusEnum { Active, Inactive }
var source = new Source { Age = 25, Status = "Active" };
var target = mapper.Map<Source, Target>(source);
// target.Age = "25"
// target.Status = StatusEnum.Active
TurboMapper gracefully handles null values:
// Null source returns null
Source source = null;
var target = mapper.Map<Source, Target>(source); // returns null
// Null nested objects are preserved
var person = new Person { Name = "John", Address = null };
var mapped = mapper.Map<Person, Person>(person);
// mapped.Address is null
Configure custom mappings without modules:
using TurboMapper.Impl;
var mapper = new Mapper();
var mappings = new List<PropertyMapping>
{
new PropertyMapping
{
SourcePropertyPath = "FirstName",
TargetPropertyPath = "FullName"
},
new PropertyMapping
{
SourcePropertyPath = "Age",
TargetPropertyPath = "Years"
}
};
mapper.CreateMap<UserSource, UserTarget>(mappings);
var source = new UserSource { FirstName = "John", Age = 30 };
var target = mapper.Map<UserSource, UserTarget>(source);
// target.FullName = "John"
// target.Years = 30
Map nested properties to flat structure:
public class Source
{
public string Name { get; set; }
public Address Address { get; set; }
}
public class FlatTarget
{
public string Name { get; set; }
public string Street { get; set; }
public string City { get; set; }
}
var mappings = new List<PropertyMapping>
{
new PropertyMapping
{
SourcePropertyPath = "Address.Street",
TargetPropertyPath = "Street"
},
new PropertyMapping
{
SourcePropertyPath = "Address.City",
TargetPropertyPath = "City"
}
};
mapper.CreateMap<Source, FlatTarget>(mappings);
Map flat structure to nested objects:
public class TargetWithNested
{
public string Name { get; set; }
public AddressConfig Address { get; set; }
}
public class AddressConfig
{
public string StreetName { get; set; }
public string Location { get; set; }
}
var mappings = new List<PropertyMapping>
{
new PropertyMapping
{
SourcePropertyPath = "Address.Street",
TargetPropertyPath = "Address.StreetName"
},
new PropertyMapping
{
SourcePropertyPath = "Address.City",
TargetPropertyPath = "Address.Location"
}
};
mapper.CreateMap<Source, TargetWithNested>(mappings);
Control which properties get mapped:
var mappings = new List<PropertyMapping>
{
new PropertyMapping
{
SourcePropertyPath = "FirstName",
TargetPropertyPath = "Name"
}
};
// Only map explicitly defined properties
mapper.CreateMap<Source, Target>(mappings, enableDefaultMapping: false);
Map collections of objects using the updated Map method that now returns IEnumerable:
// Define your models
public class User
{
public string Name { get; set; }
public int Age { get; set; }
}
public class UserDto
{
public string Name { get; set; }
public int Age { get; set; }
}
// Create mapper instance
var mapper = serviceProvider.GetService<IMapper>();
// Map collections - the Map method now returns IEnumerable<TDestination>
var users = new List<User>
{
new User { Name = "Alice", Age = 25 },
new User { Name = "Bob", Age = 32 }
};
// Map to IEnumerable<T>
IEnumerable<UserDto> userDtos = mapper.Map<User, UserDto>(users);
// Convert to List if needed
List<UserDto> userList = userDtos.ToList();
// Works with any IEnumerable
IEnumerable<User> source = users;
IEnumerable<UserDto> result = mapper.Map<User, UserDto>(source);
// Convert to array if needed
UserDto[] userArray = result.ToArray();
Skip specific properties during mapping:
// Using mapping modules
internal class UserModule : MappingModule<UserEntity, UserDto>
{
public UserModule() : base(true) { }
public override Action<IMappingExpression<UserEntity, UserDto>> CreateMappings()
{
return config =>
{
// Ignore sensitive properties
config.ForMember(dest => dest.Name, src => src.Name)
.ForMember(dest => dest.Email, src => src.Email)
.Ignore(dest => dest.Id); // Skip ID mapping
};
}
}
// Using IMappingExpression directly
var expression = new MappingExpression<UserEntity, UserDto>();
expression.ForMember(dest => dest.Name, src => src.Name)
.Ignore(dest => dest.Password) // Skip password mapping
.Ignore(dest => dest.CreatedAt); // Skip timestamp
Register custom type conversion functions within mapping modules:
// Define a mapping module that registers converters
internal class OrderMappingModule : MappingModule<OrderEntity, OrderDto>
{
public OrderMappingModule() : base(true)
{
// Register converters within the module
RegisterConverter<string, Guid>(str => Guid.Parse(str));
RegisterConverter<string, DateTime>(str => DateTime.Parse(str));
RegisterConverter<DateTime, string>(dt => dt.ToString("yyyy-MM-dd"));
}
public override Action<IMappingExpression<OrderEntity, OrderDto>> CreateMappings()
{
return config =>
{
// Define any custom property mappings if needed
// Otherwise, default mapping will apply with custom converters available
};
}
}
// The custom converters will be registered when the module is loaded
// Use the custom converters in mapping
public class OrderEntity
{
public string OrderId { get; set; }
public string OrderDate { get; set; }
}
public class OrderDto
{
public Guid OrderId { get; set; }
public DateTime OrderDate { get; set; }
}
var entity = new OrderEntity
{
OrderId = "12345678-1234-1234-1234-123456789012",
OrderDate = "2024-01-15"
};
var dto = mapper.Map<OrderEntity, OrderDto>(entity);
// Custom converters automatically convert string to Guid and string to DateTime
Map properties based on conditions:
// Using mapping modules
internal class ConditionalUserModule : MappingModule<UserEntity, UserDto>
{
public ConditionalUserModule() : base(true) { }
public override Action<IMappingExpression<UserEntity, UserDto>> CreateMappings()
{
return config =>
{
// Only map premium features if user is premium
config.ForMember(dest => dest.Name, src => src.Name)
.When(dest => dest.PremiumFeatures, src => src.IsPremium)
.When(dest => dest.AccountLevel, src => src.Age > 18);
};
}
}
// Using IMappingExpression directly
var expression = new MappingExpression<UserEntity, UserDto>();
expression.ForMember(dest => dest.Name, src => src.Name)
.When(dest => dest.Status, src => src.Age >= 18) // Map status only if adult
.When(dest => dest.PremiumFeatures, src => src.IsPremium); // Map premium features conditionally
// Example models for conditional mapping
public class UserEntity
{
public string Name { get; set; }
public int Age { get; set; }
public bool IsPremium { get; set; }
}
public class UserDto
{
public string Name { get; set; }
public string Status { get; set; } // Only mapped if Age >= 18
public List<string> PremiumFeatures { get; set; } = new List<string>(); // Only mapped if IsPremium
}
Transform values during mapping:
// Using mapping modules
internal class TransformModule : MappingModule<UserEntity, UserDto>
{
public TransformModule() : base(true) { }
public override Action<IMappingExpression<UserEntity, UserDto>> CreateMappings()
{
return config =>
{
config.ForMember(dest => dest.Name, src => src.Name)
.MapWith<UserEntity, string>(dest => dest.Name, src => $"Mr./Ms. {src.Name}")
.MapWith<UserEntity, string>(dest => dest.Email, src => src.Email.ToLower())
.MapWith<UserEntity, int>(dest => dest.Age, src => src.Age + 1); // Add 1 to age
};
}
}
// Using IMappingExpression directly
var expression = new MappingExpression<UserEntity, UserDto>();
expression.ForMember(dest => dest.Name, src => src.Name)
.MapWith<UserEntity, string>(dest => dest.Greeting, src => $"Hello, {src.Name}")
.MapWith<UserEntity, string>(dest => dest.Slug, src => src.Name.Replace(" ", "-").ToLower());
// Example: Transform phone numbers to masked format
public class ContactEntity
{
public string Name { get; set; }
public string Phone { get; set; } // e.g., "123-456-7890"
}
public class ContactDto
{
public string Name { get; set; }
public string Phone { get; set; } // e.g., "###-###-7890"
}
// Module to mask phone numbers
internal class ContactModule : MappingModule<ContactEntity, ContactDto>
{
public override Action<IMappingExpression<ContactEntity, ContactDto>> CreateMappings()
{
return config =>
{
config.ForMember(dest => dest.Name, src => src.Name)
.MapWith<ContactEntity, string>(dest => dest.Phone, src =>
string.IsNullOrEmpty(src.Phone) ? src.Phone :
$"###-###-{src.Phone.Substring(src.Phone.Length - 4)}");
};
}
}
Validate mapping configurations at runtime:
// Get mapper instance
var mapper = serviceProvider.GetService<IMapper>();
// Create a mapping configuration
mapper.CreateMap<UserEntity, UserDto>(new List<PropertyMapping>
{
new PropertyMapping
{
SourcePropertyPath = "Name",
TargetPropertyPath = "FullName"
}
});
// Validate the configuration before using it
var validationResult = mapper.ValidateMapping<UserEntity, UserDto>();
if (!validationResult.IsValid)
{
foreach (var error in validationResult.Errors)
{
Console.WriteLine($"Mapping error: {error}");
}
}
// Example: Validating in application startup
public void ConfigureServices(IServiceCollection services)
{
services.AddTurboMapper();
var mapper = services.BuildServiceProvider().GetService<IMapper>();
// Validate critical mappings
var validationResult = mapper.ValidateMapping<OrderEntity, OrderDto>();
if (!validationResult.IsValid)
{
throw new InvalidOperationException($"Invalid order mapping: {string.Join(", ", validationResult.Errors)}");
}
}
TurboMapper now handles nullable to non-nullable type conversions seamlessly:
public class Source
{
public string Name { get; set; }
public int? Age { get; set; }
public DateTime? BirthDate { get; set; }
}
public class Target
{
public string Name { get; set; }
public int Age { get; set; } // Non-nullable
public DateTime BirthDate { get; set; } // Non-nullable
}
var source = new Source
{
Name = "John",
Age = 30, // Has value
BirthDate = null // Is null
};
var target = mapper.Map<Source, Target>(source);
// target.Name = "John"
// target.Age = 30
// target.BirthDate = DateTime.MinValue (default value for DateTime)
Mapping modules provide a clean, reusable way to organize mapping configurations.
using TurboMapper;
using System;
internal class UserMappingModule : MappingModule<UserSource, UserTarget>
{
public UserMappingModule() : base(enableDefaultMapping: true)
{
}
public override Action<IMappingExpression<UserSource, UserTarget>> CreateMappings()
{
return config =>
{
config.ForMember(dest => dest.Name, src => src.FirstName)
.ForMember(dest => dest.Years, src => src.Age);
};
}
}
internal class AddressMappingModule : MappingModule<AddressSource, AddressTarget>
{
public AddressMappingModule() : base(enableDefaultMapping: true)
{
}
public override Action<IMappingExpression<AddressSource, AddressTarget>> CreateMappings()
{
return config =>
{
config.ForMember(dest => dest.Address.Street, src => src.Address.StreetName)
.ForMember(dest => dest.Address.Location, src => src.Address.City);
};
}
}
When you want complete control over what gets mapped:
internal class StrictMappingModule : MappingModule<Source, Target>
{
public StrictMappingModule() : base(enableDefaultMapping: false)
{
}
public override Action<IMappingExpression<Source, Target>> CreateMappings()
{
return config =>
{
config.ForMember(dest => dest.Name, src => src.FirstName)
.ForMember(dest => dest.Years, src => src.Age);
// Only these properties will be mapped
};
}
}
Mapping modules are automatically discovered and registered when using dependency injection:
services.AddTurboMapper();
// All mapping modules in loaded assemblies are automatically registered
using Microsoft.Extensions.DependencyInjection;
using TurboMapper;
var services = new ServiceCollection();
services.AddTurboMapper();
var serviceProvider = services.BuildServiceProvider();
The mapper is registered as a singleton and thread-safe:
var mapper1 = serviceProvider.GetService<IMapper>();
var mapper2 = serviceProvider.GetService<IMapper>();
// mapper1 and mapper2 are the same instance
// Startup.cs or Program.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddTurboMapper();
services.AddControllers();
}
// In a controller
public class UserController : ControllerBase
{
private readonly IMapper _mapper;
public UserController(IMapper mapper)
{
_mapper = mapper;
}
[HttpPost]
public IActionResult CreateUser(UserDTO dto)
{
var entity = _mapper.Map<UserDTO, UserEntity>(dto);
// Save entity
return Ok();
}
}
The mapper works correctly across service scopes:
using (var scope1 = serviceProvider.CreateScope())
using (var scope2 = serviceProvider.CreateScope())
{
var mapper1 = scope1.ServiceProvider.GetService<IMapper>();
var mapper2 = scope2.ServiceProvider.GetService<IMapper>();
// Both resolve to the same singleton instance
}
TurboMapper is fully thread-safe for concurrent mapping operations:
var sources = GetLargeDataSet();
var results = new ConcurrentBag<Target>();
Parallel.ForEach(sources, source =>
{
var target = mapper.Map<Source, Target>(source);
results.Add(target);
});
For large datasets, process in batches:
var sources = GetLargeDataSet(); // 10,000 items
var targets = new List<Target>(sources.Count);
foreach (var source in sources)
{
targets.Add(mapper.Map<Source, Target>(source));
}
Always reuse the same mapper instance (singleton pattern):
// Good - Reuse singleton
var mapper = serviceProvider.GetService<IMapper>();
for (int i = 0; i < 1000; i++)
{
var result = mapper.Map<Source, Target>(sources[i]);
}
// Bad - Creating new instances
for (int i = 0; i < 1000; i++)
{
var mapper = new Mapper(); // Don't do this
var result = mapper.Map<Source, Target>(sources[i]);
}
Mapping configurations are cached internally, so repeated mappings are efficient:
mapper.CreateMap<Source, Target>(mappings);
// Subsequent calls use cached configuration
for (int i = 0; i < 10000; i++)
{
mapper.Map<Source, Target>(sources[i]); // Fast
}
public interface IMapper
{
TTarget Map<TSource, TTarget>(TSource source);
IEnumerable<TDestination> Map<TSource, TDestination>(IEnumerable<TSource> source);
}
Map Method (Single Object)
-
Parameters:
-
TSource source
- The source object to map from
-
-
Returns:
TTarget
- The mapped target object - Returns null if: Source is null
Map Method (Collection)
-
Parameters:
-
IEnumerable<TSource> source
- The collection of source objects to map from
-
-
Returns:
IEnumerable<TDestination>
- The mapped collection of target objects - Returns null if: Source is null
public interface IMappingExpression<TSource, TTarget>
{
IMappingExpression<TSource, TTarget> ForMember<TValue>(
Expression<Func<TTarget, TValue>> targetMember,
Expression<Func<TSource, TValue>> sourceMember);
}
ForMember Method
- Configures custom mapping between properties
- Supports nested property paths
- Returns the expression for fluent chaining
public abstract class MappingModule<TSource, TTarget>
{
protected MappingModule(bool enableDefaultMapping = true);
public abstract Action<IMappingExpression<TSource, TTarget>> CreateMappings();
}
Constructor Parameters
-
enableDefaultMapping
- When true, unmapped properties use name-based mapping
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddTurboMapper(
this IServiceCollection services);
}
AddTurboMapper Method
- Registers IMapper as singleton
- Auto-discovers and registers mapping modules
- Returns IServiceCollection for chaining
internal class PropertyMapping
{
public string SourcePropertyPath { get; set; }
public string TargetPropertyPath { get; set; }
}
Always register TurboMapper with DI for automatic module discovery:
services.AddTurboMapper();
Group related mappings in modules:
// UserMappings.cs
internal class UserDTOMappingModule : MappingModule<UserDTO, UserEntity>
{
// ...
}
internal class UserEntityMappingModule : MappingModule<UserEntity, UserDTO>
{
// ...
}
Unless you need strict control, enable default mapping:
public MyMappingModule() : base(enableDefaultMapping: true)
{
// Only specify differences from default
}
Always check for null in business logic when needed:
var entity = mapper.Map<DTO, Entity>(dto);
if (entity?.Address == null)
{
entity.Address = new Address();
}
Leverage lambda expressions for compile-time safety:
config.ForMember(dest => dest.FullName, src => src.FirstName)
.ForMember(dest => dest.Years, src => src.Age);
Always unit test custom mappings:
[Test]
public void Map_UserDTOToEntity_MapsCorrectly()
{
var dto = new UserDTO { FirstName = "John", Age = 30 };
var entity = mapper.Map<UserDTO, UserEntity>(dto);
Assert.AreEqual("John", entity.Name);
Assert.AreEqual(30, entity.Years);
}
Keep object hierarchies reasonable for performance:
// Good - 2-3 levels
User.Address.City
// Potentially slow - 5+ levels
User.Profile.Settings.Preferences.Display.Theme
Don't create mappings repeatedly:
// Good - Create once
mapper.CreateMap<Source, Target>(mappings);
// Bad - Inside loop
for (int i = 0; i < 1000; i++)
{
mapper.CreateMap<Source, Target>(mappings); // Don't do this
}
Problem: Properties with matching names aren't mapping.
Solutions:
- Ensure properties have public getters and setters
- Check property names match exactly (case-sensitive)
- Verify target property has a setter (
set
accessor)
// Won't map - no setter
public class Target
{
public string Name { get; } // Read-only
}
// Will map - has setter
public class Target
{
public string Name { get; set; }
}
Problem: Nested objects aren't being created.
Solutions:
- Check if source nested object is null
- Ensure nested class has parameterless constructor
- Verify property types match or are compatible
public class Address
{
// Must have parameterless constructor
public Address() { }
public string Street { get; set; }
}
Problem: Automatic type conversion not working.
Solutions:
- Check if types are compatible for conversion
- Implement explicit mapping for complex conversions
- Ensure enum names match string values (case-insensitive)
// Works - Compatible types
int age = 25;
string ageStr = age.ToString(); // TurboMapper handles this
// May fail - Incompatible types
object obj = new MyClass();
int value = (int)obj; // Need explicit handling
Problem: Mapping module not being discovered.
Solutions:
- Ensure module class is not abstract
- Make module class internal or public (not private)
- Verify assembly is loaded in AppDomain
- Check that AddTurboMapper() is called
// Correct - Will be discovered
internal class MyMappingModule : MappingModule<Source, Target>
{
// ...
}
// Wrong - Won't be discovered
private class MyMappingModule : MappingModule<Source, Target>
{
// ...
}
Problem: Mapping is slow with large datasets.
Solutions:
- Reuse mapper instance (singleton)
- Configure mappings once, not repeatedly
- Use Parallel processing for large collections
- Profile to identify specific bottlenecks
// Efficient approach
var mapper = serviceProvider.GetService<IMapper>();
var results = sources
.AsParallel()
.Select(s => mapper.Map<Source, Target>(s))
.ToList();
Wrap mapping calls to get better error messages:
try
{
var result = mapper.Map<Source, Target>(source);
}
catch (Exception ex)
{
Console.WriteLine($"Mapping failed: {ex.Message}");
Console.WriteLine($"Stack trace: {ex.StackTrace}");
throw;
}
Test that your configuration is registered:
var mapper = serviceProvider.GetService<IMapper>();
var source = new Source { Name = "Test" };
try
{
var target = mapper.Map<Source, Target>(source);
Console.WriteLine("Mapping successful");
}
catch
{
Console.WriteLine("Mapping configuration missing");
}
Verify assemblies containing modules are loaded:
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (var assembly in assemblies)
{
Console.WriteLine($"Loaded: {assembly.FullName}");
}
public class CreateUserRequest
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
}
public class User
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public DateTime CreatedAt { get; set; }
}
// Mapping
var request = new CreateUserRequest
{
FirstName = "John",
LastName = "Doe",
Email = "[email protected]"
};
var user = mapper.Map<CreateUserRequest, User>(request);
user.CreatedAt = DateTime.UtcNow;
public class OrderDTO
{
public string OrderNumber { get; set; }
public CustomerDTO Customer { get; set; }
public List<OrderItemDTO> Items { get; set; }
}
public class OrderEntity
{
public string OrderNumber { get; set; }
public CustomerEntity Customer { get; set; }
public List<OrderItemEntity> Items { get; set; }
}
// Map with nested objects
var dto = GetOrderDTO();
var entity = mapper.Map<OrderDTO, OrderEntity>(dto);
// Map collection
entity.Items = dto.Items
.Select(item => mapper.Map<OrderItemDTO, OrderItemEntity>(item))
.ToList();
internal class OrderFlatteningModule : MappingModule<Order, OrderFlat>
{
public OrderFlatteningModule() : base(true)
{
}
public override Action<IMappingExpression<Order, OrderFlat>> CreateMappings()
{
return config =>
{
config.ForMember(dest => dest.CustomerName, src => src.Customer.Name)
.ForMember(dest => dest.CustomerEmail, src => src.Customer.Email)
.ForMember(dest => dest.ShippingStreet, src => src.ShippingAddress.Street)
.ForMember(dest => dest.ShippingCity, src => src.ShippingAddress.City);
};
}
}
- Performance Improvements: Significant performance enhancements (2x+) through compiled expression trees and metadata caching
- Collection Mapping Support: Added Map method overload to IMapper for collection mapping operations that returns IEnumerable
- Ignored Properties Option: Added Ignore method to IMappingExpression to skip properties during mapping
- Custom Type Converters Registration: Added RegisterConverter method to MappingModule for custom type conversion functions that can be registered with type mappings
- Improved Nullable Type Handling: Enhanced ConvertValue method to handle nullable types properly
- Conditional Mapping: Added When method to IMappingExpression for conditional property mapping
- Mapping Transformations: Added MapWith method for transformation functions during mapping
- Comprehensive Type Conversions: Enhanced ConvertValue with DateTime, TimeSpan, and other common type conversions
- Configuration Validation: Added ValidateMapping method to IMapper that returns ValidationResult for early validation
- Improved Error Messages: Better debugging information for conversion failures
- Refactored Code: Consolidated duplicate GetMemberPath methods into shared internal implementation
- Optimized Object Creation: Replaced Activator.CreateInstance with factory delegates for faster instantiation
- Configuration Caching: Added caching for mapping configurations and lookups for faster performance
- Backward Compatibility: Maintains full backward compatibility with v1.0.0
- Initial release
- Default name-based mapping
- Custom property mapping
- Nested object support
- Type conversion
- Mapping modules
- Dependency injection integration
- Multi-framework support
TurboMapper is licensed under the terms specified in the LICENSE file.
For issues, questions, or contributions:
- GitHub: https://github.com/CodeShayk/TurboMapper
- Wiki: https://github.com/CodeShayk/TurboMapper/wiki
Contributions are welcome! Please submit pull requests or open issues on GitHub.