-
Notifications
You must be signed in to change notification settings - Fork 1
Home
FluentMapper is an object-to-object mapping library, similar in purpose to both AutoMapper and TinyMapper. However, FluentMapper is designed to offer two key benefits:
- FluentMapper is configured via a fluent API (hence the name)
- FluentMapper is opinionated (see below)
At its core, object-to-object (o2o) mapping is simply copying data from one object to another. In FluentMapper, we refer to these two objects as the source (where the data comes from) and the target (where the data goes to). Writing o2o mapping code by hand is really, really boring. Code like this is tedious to write, and tedious to update as the source and target classes get updated:
target.Prop1 = source.Prop1;
target.Prop2 = source.Prop2;
// ad nauseum
This gets further complicated by the fact that as the source and target classes change, some bad things can happen in your manual o2o mapping code:
- Your code doesn't compile anymore because a property has been removed or renamed.
- Your code doesn't copy data from the source to the target when new properties are added to the source.
- Your code doesn't inform you when new properties appear on the target and are left uninitialized.
To create a mapping in FluentMapper, let's assume a couple of classes called Source
and Target
:
public class Source
{
public string A { get; set; }
public int B { get; set; }
}
public class Target
{
public string A { get; set; }
public int B { get; set; }
}
The configuration to establish a mapping for this is quite simple:
var mapper = FluentMapper
.ThatMaps<Target>().From<Source>()
.Create();
You can then use the mapper
object to create instances of Target
via instances of Source
:
Target target = mapper.Map(new Source { A = "string value", B = 7654 });
Assert.AreEqual(target.A, source.A); // yep, they're equal
Assert.AreEqual(target.B, source.B); // same thing
Now, let's say that we modify the Source
class to look like this:
public class Source
{
public string A { get; set; }
public int BProp { get; set; }
}
The mapping above won't work, and will throw an exception inside the Create()
method. Instead, we have to inform FluentMapper what the relationship should be between the properties of the two classes:
var mapper = FluentMapper
.ThatMaps<Target>().From<Source>()
.ThatSets(tgt => tgt.B).From(src => src.BProp)
.Create();
Now this mapper can be used in just the same way:
Target target = mapper.Map(new Source { A = "string value", BProp = 7654 });
Assert.AreEqual(target.A, source.A); // yep, they're equal
Assert.AreEqual(target.B, source.BProp); // same thing
FluentMapper is opinionated about o2o mapping, and therefore, you, as the programmer, must account for every public property on both the source and target types.
Every. single. property?
Yep. Every one. If a new property gets added or an old property gets deleted, FluentMapper will throw an exception until you update your mapping configuration to account for those changes. Therefore, it would be extremely wise to put your FluentMapper mapping configuration into some automated tests.
FluentMapper configuration is intended to be put under automated test.
The first advantage of FluentMapper's strong opinions is that your codebase informs you when you have stale mappings that need to be updated. Generally, we programmers try not to make code changes unless they're important. FluentMapper assumes that when you add properties or remove them, those changes are important, and the implications of those changes in your o2o mappings are also important. If important data gets silently ignored, FluentMapper thinks that's A Bad Thing (tm).
The next advantage is that your FluentMapper configuration will be exactly as complex as the relationship between your source and target types. If Source
and Target
have complex rules, that complexity isn't hidden inside a large swath of boilerplate. For instance, consider this manual o2o code:
target.FirstName = source.FirstName;
target.LastName = source.LastName;
target.Gender = source.Gender;
target.Height = source.Height * 100;
target.Weight = source.Weight;
target.BirthDate = source.BirthDate;
There's some logic hiding in there that could easily get overlooked. In FluentMapper, the extra logic is front-and-center:
var mapper = FluentMapper
.ThatMaps<Target>().From<Source>()
.ThatSets(tgt => tgt.Height).From(src => src.Height * 100)
.Create();