There are many Mapper library found in the world. AutoMapper, TinyMapper, Mapster, AgileMapper, ExpressMapper, FastMapper...etc. I found that other library has 3 missing feature.
- Cause StackoverflowException with recursive referenced object.
- List property that has private setter.
- Dictionary to target object
All library cause StackOverflowException when processing recursive object. Even though List with private setter is very common class design, some library does not copy element to List private setter. Some library does not map from Dictionary to object.
You can see the reproduct code of these problem at
So I started to try to create another maping library for my study. It is my hobby project. But I want to share about my experience in this work for other developer something may help them. You can get HigLabo.Mapper from Nuget.
How it works?
HigLabo.Mapper is using IL generator under the food. It generate code to map object. You can see all generate logic at
ObjectMapConfig class of
CreateMapPropertyMethod method create actual IL code for each type mapping. I'll describe these internal logic spec later chapter.
2. Zero Configuration
Other some existing library need configuration for mapping. I try to reduce configuration on
HigLabo.Mapper. As a result, zero configuration is achieved(for 80% general usage). In a while, full customization by configuration for other cases(20% specific usage).
In general cases, AutoMapper required Initialize mapping between two types like this.
And call map method.
var customerDto = AutoMapper.Mapper.Map<CustomerDTO>(customer);
TinyMapper also require to call
Bind method in multi thread environment.
If you have 100 domain object in your applicatoin, it takes a lot time to write initialization code.
HigLabo.Mapper does not required any initialization code. Just you have to do is to call map method.
var customerDto = config.Map(customer, new CustomerDTO());
You can call by extension method like this by using extension method. Inside extension method,
ObjectMapConfig.Current is used.
var customerDto = customer.Map(new CustomerDTO());
HigLabo.Mapper will map same name property automatically and handle child object and child collections. You can change all mapping rule by
PropertyMapRule class like prefix, suffix, underscore or other. You can also do custom mapping like ignore some property or different property name map by calling
AddPostAction method. I'll explain it in later
3. Mapping specification
This chapter, I'll explain spec about type mapping rule. I defined these rule by test code. You can see these test code at https://github.com/higty/higlabo/blob/master/HigLabo.Test/HigLabo.Mapper.Test/TestCase/ObjectMapConfigTest.cs.
3.1 Basic mapping for same property names
HigLabo.Mapper will map same name properties.You can see this spec as test code at these method.
3.2 Null handling when source is null
MapOrNull method return null when source is null. If source is not null, call Map. You can see this spec as test code at these method.
3.3 Null handling when source property is null
If source property is null (class or Nullable<>), we want to set target property to null. You can see this spec as test code at these method.
3.4 Nullable handling(target property is null)
Other case is target property type is class (except string) but source property value could not convert to target property type. In this case, we handle it 3 ways as below.
- Do nothing. Keep target proeprty is null.
- If target type has default constructor, it will create new object to target property and call map method between source property value and this new object on target property.
- Copy reference of source property value to target property (if source type is assignable to target type).
By default, if target property is null, create new object if object has default constructor. You can change this behavior by setting
NullPropertyMapMode property of
ObjectMapConfig. You can see this spec as test code at these method.
3.5 Between Dictionary and Object mapping
If source is Dictionary, we want to map by indexer to target object. And we also want to map from object to Dictionary. You can see this spec as test code at these method.
3.6 Handling when that source property value can not be converted to target property type
For example, source value is "abc" and target type is int, we want to fail map. Other case is from decimal to int. Actually in these cases, we want to do nothing for target property. You can see this spec as test code at these method.
3.7 Encoding object
You want to convert string like "UTF-8" to Encoding object. I create this feature achieved by TypeConverter object. You can see this spec as test code at these method.
Later chapter, I'll explain custom TypeConverter.
3.8 Dynamic object
HigLabo.Mapper support dynamic object. You can see this spec as test code at these method.
3.9 IDataReader object
IDataReader object. You can pass
IDataReader object to
Map method. Inside
IDataReader will be converted to
Dictionary and call
Map method. You can map each database column to property name by
AddPostAction, such as
database_id column to
DatabaseID property. You can see this spec as test code at these method.
3.10 List to List
List property need some consideration. We must consider about
- map collection element or not.
- map collection element by create new object or copy reference for each elements.
To manage these mapping configuration, you set
CollectionElementMapMode property of
ObjectMapConfig class. If
CollectionElementMapMode is None, any element is not copied to target property. If
NewObject, create new object and call
Map method. If
CollectionElementMapMode is DeepCopy, reference is copied to target collection. You can see this spec as test code at these method.
3.11 Flatten mapping
Flatten mapping is supported. You can flatten from source object to target object like this.
var config = new ObjectMapConfig();
config.AddPostAction<User, UserFlatten>((source, target) =>
Look like easy to understand compare other library. If you use AutoMapper, you must know about
ResolveUsing ...etc. Just you must know is source is User and target is
UserFlatten and just write usual C# code to flatten object. You can see this spec as test code at these method.
3.12 Custom conversion
You can add common custome conversion when you convert source object to target types. If default conversion is failed, your custom convertsion will be tried to convert source object. You can see this spec as test code at these method.
If you find missing pattern, please send me a test code on GitHub.
4. Full Customization
You can customize all behavior by using
RemovePropertyMap. You can change map rule by calling
AddPostActionMethod. If you want to convert your custom logic, you write code like this.
config.AddPostAction<String, DayOfWeek>((source, target) =>
return DayOfWeekConverter(source) ?? target;
config.AddPostAction<Person1, Person2>((source, target) =>
target.FullName = source.FamilyName + " " + source.FirstName;
You can ignore default property mapping by call
RemovePropertyMap method. It would be better to use nameof keyword rather than string to protect runtime error when you change property name.
config.RemovePropertyMap<User, User>(nameof(User.DecimalNullable), "DateTimeNullable", "DayOfWeekNullable");
You can see this spec as test code at these method.
You can replace all logic by call
ReplacePropertyMap method by passing your custom action against source and target. That's all and It is easy to understand than other library.
You can see this spec as test code at these method.
TypeConverter is declared in
TypeConverter class handle all premitive class and Encoding object to convert from object to target types. You can customize by inherit from
TypeConverter object and override method. And set your custom
TypeConverter class to ObjectMapConfig.TypeConverter property.
As you can see in early chapter, you can use
HigLabo.Mapper without configuration. But if it is not suite your requirement, you can customize property mapping rule. If you want to map all Value property to target property by using
PropertyNameMappingRule. You can use
SuffixPropertyMappingRule to map prefix, suffix properties. You can see this spec as test code at these method.
If you would like to customize mapping rule between
Dictionary(Dictionary<String, String> or
Dictionary<String, Object>) and object, you can do it by using
DictionaryMappingRules property of
ObjectMapConfig object. You can assign dictionary key and property name by
DictionaryKeyMappingRule object to customize your own mapping rule. And you also add your custom
DictionaryMappingRule object to map Dictionary and Object. You can see this spec as test code at these method.
8. Extension methods
You can use Map extension method against object.
var target = source.Map(new TTarget());
All extension method is declared in
ObjectMapExtensions class. These extension method use
ObjectMapConfig. Current object to map source and target object.
9. Multiple map rule
You can create multiple
ObjectMapConfig instance. You can set
CollectionElementMapMode for each object, such as first one create new object, another one deep copy.
10. Performance comparison
Performance is critical for mapper library. Mapper is tendecy to used in a hot path (such as foreach statement to process collection from databases) in your application. Unfortunately, HigLabo.Mapper does not faster than other project. I created performance test project at https://github.com/higty/higlabo/tree/master/HigLabo.Test/HigLabo.Mapper.PerformanceTest
Here is a test code for performance by BenchmarkDotNet.
All entity classes are here.
Here is a test result for basic 1000 times loop.
Method | Mean | StdDev | Gen 0 | Allocated |
------------------ |-------------- |----------- |--------- |---------- |
HigLaboMapperTest | 2,107.1849 us | 24.4056 us | 83.3333 | 497.55 kB |
TinyMapperTest | 657.8088 us | 8.1006 us | 67.1875 | 273.55 kB |
AutoMapperTest | 752.8322 us | 3.1900 us | 53.5156 | 229.55 kB |
MapsterTest | 774.9905 us | 7.5189 us | 63.8021 | 261.55 kB |
AgileMapperTest | 1,065.5490 us | 14.5767 us | 223.1771 | 825.57 kB |
ExpressMapperTest | 1,325.6581 us | 15.3520 us | 88.5417 | 393.54 kB |
FastMapperTest | 1,481.9169 us | 15.6490 us | 199.7396 | 757.57 kB |
If you need ultimately speed, you would better to select
TinyMapper. I found that
TinyMapper is fastest. As you can see,
HigLabo.Mapper is not fast due to prevent from StackoverflowException by calling
Map method recursively. Map method call cause 1ms slower performance penalty but it is necessary. Other library may compile inline to child object and that may cause StackoverflowException.
11. Deep dive to internal code
ILGenerator. The main methods are
CreateMethod.CreatePropertyMaps method create mapping information by reflection. Mapping is based on
ObjectMapConfig manage these classes that you can set by
CreateMethod generate IL code from collection of
DictionaryMappingRule. The basic implementation of map code is like below. (Conceptual code)
source.P1 --> target.P1;
source.P1 --> target["P1"];
source["P1"] --> target.P1;
source["P1"] --> target["P1"];
context --> MappingContext.
if (typeof(source) == typeof(target))
target.P1 = source.P1;
else if (Use TypeConverter for primitive types)
var converted = converter.ToXXX(source.P1);
if (converted != null)
target.P1 = converted;
target.P1 = source["P1"];
if (target property is Class)
case NullPropertyMapMode.NewObject: target.P1 = new XXX(); break;
if (typeof(source) inherit from typeof(parent))
target.P1 = source.P1;
if (source type is IEnumerable and target type is ICollection)
case CollectionElementMapmode.NewObject: this.MapElement(source, target); break;
case CollectionElementMapmode.CopyReference: this.MapDeepCopy(source, target); break;
target.P1 = source.P1.Map(target.P1);
CreateMethod has four parameters. First parameter is
ObjectMapConfig. Second parameter is source object and Third is target object. 4th parameter is
MappingContext that has some information to prevent from infinite loop.
I created il code for each target types. Types are String, Encoding, Int32, Guid, Boolean..etc, Nullable<>,Collection,Array...etc.
If source property type is string and target property type is string, IL code is like below.
var sourceGetMethod = sourceProperty.PropertyInfo.GetGetMethod();
var targetSetMethod = targetProperty.PropertyInfo.GetSetMethod();
Other type il code is very straight forward way. Perhaps you can easily read my code than other library.
After copied value from source property to target property, Call Map method if target type is class(Complex type) to copy child properties. I add constraint to generic Map_XXX method to avoid performance penalty. But still slow to call Map method. It is why I could not reach other library's performance.
Any help is great, I really apreciate for your help. I searched to improve Map method call performance but I could not found a way. It is not fast, but never StackoverflowException, List private setter available, Dictionary (DataReader also) mapping, easy full customization by
I'm glad to use my library and all your feedback or star by GitHub repository. I continue my effort to reach AutoMapper feature or TinyMapper performance.
Thank you for reading my article.
2016.12.07 Initial post.