Super-simple Object Mapper

If you need a full-featured object mapper with minimal set up, I recommend you take a look at AutoMapper on Codeplex.  If you need a quick-and-dirty solution, maybe the following code could help you out.

First off, why am I not using AutoMapper?  At my current client, there are strict rules as to the use of open source software.  There's a process in place for requesting the use of a particular open source tool, but with the red-tape of the approval process (reviews, signatures, justification, etc…), it could literally take 3 – 6 months.  It's just not worth it for what I need right now.  So I rolled my own.

This mapper is super-simple, not very smart and may have a bug or two in it, but it works for what I need it to do and reduces a lot of hand coding.  USE AT YOUR OWN RISK!

It uses two simple rules to map data between two objects:

  1. If a property name and type on the source match the name and type of a destination property, the value is copied.
  2. If the user has defined a custom mapping action, use that to copy data (but rule #1 is always executed first).

Let's dig into the details.

First, I set up a generic class that takes in a couple of types – my source and destination types.  I added a clause on the destination type that it must be 'new-able' so that I could provide a utility function that would create a destination object, map it's values from a source and return it to you.

public class Mapper<TSource, TDest> where TDest : new()
{
 
}

Copying Properties

When copying data from a source object to a destination object, we get all public instance properties of the destination and see if they have a matching (same name and same type) property on the source.  If so, we set the value on our destination object:

protected virtual void CopyMatchingProperties(TSource source, TDest dest)
{
    foreach(var destProp in typeof(TDest).GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p => p.CanWrite))
    {
        var sourceProp =
            typeof (TSource).GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p => p.Name == destProp.Name && p.PropertyType == destProp.PropertyType).
                FirstOrDefault();
        if( sourceProp != null)
        {
            destProp.SetValue(dest, sourceProp.GetValue(source, null), null);
        }
    }
}

Custom Transformations

I want the ability to define my own transformation for special cases.  This is simply a list of Action<TSource, TDest> delegates:

protected readonly IList<Action<TSource, TDest>> mappings = new List<Action<TSource, TDest>>();
 
public virtual void AddMapping(Action<TSource, TDest> mapping)
{
    mappings.Add(mapping);
}

Again, simple yet functional.

Perform Mappings

The last thing we need is a couple of methods to execute the actual mapping:

public virtual TDest MapObject(TSource source, TDest dest)
{
    CopyMatchingProperties(source, dest);
    foreach(var action in mappings)
    {
        action(source, dest);
    }
 
    return dest;
}
 
public virtual TDest CreateMappedObject(TSource source)
{
    TDest dest = new TDest();
    return MapObject(source, dest);
}

You'll see that "CreatedMappedObject" was the reason we needed to have the new-able clause on the TDest generic parameter.

Usage

Now let's put this into action!  Given a simple domain object and view model:

public class DomainObject
{
    public string Name { get; set; }
    public DateTime DOB { get; set; }
    public int Age { get; set; }
    public string Address { get; set; }
}
 
public class ViewModel
{
    public string Name { get; set; }
    public int Age { get; set; }
}

As you can see, our view model only needs the Name and Age.  Our mapping code looks like this:

var mapper = new Mapper<DomainObject, ViewModel>();
var viewModel = mapper.CreateMappedObject(domainObject);

if the view model is created somewhere else and pre-populated with other data, we would use the MapObject method instead of creating a new instance of ViewModel:

var mapper = new Mapper<DomainObject, ViewModel>();
var viewModel = InitializeViewModel();
viewModel = mapper.MapObject(domainObject, viewModel);

Now let's assume we want to add the user's birth year to the view:

public class ViewModel
{
    public string Name { get; set; }
    public int Age { get; set; }
    public int BirthYear { get; set; }
}

Yes, we could pass along the entire date of birth, but this way the view model is getting only what it needs and doesn't need to do any additional processing to get the year:

var mapper = new Mapper<DomainObject, ViewModel>();
mapper.AddMapping((source,dest) => dest.BirthYear = source.DOB.Year);
var viewModel = mapper.CreateMappedObject(domainObject);

To encourage re-use and centralize the setup of any custom transformations, I create a subclass of my Mapper class:

public class DomainModelToViewModelMapper : Mapper<DomainObject, ViewModel>
{
    public DomainModelToViewModelMapper()
    {
        this.AddMapping((s, d) => d.BirthYear = s.DOB.Year);
    }
}

4 Comments

  • This is awesome! ;-)

  • Just curious, under what license would you be able to just grab my code as if it were something from a forum or blog post? I can change my license, as I pretty much picked the first one that wasn't GPL.

  • Jimmy,

    Thanks for inquiring. At this client, it's not a license issue (I doubt they're even aware of the various licenses available). The term "open source" is what raises all sorts of red flags.

    This is a very large automotive client and there's sticky issues with indemnification. They're more comfortable with me re-inventing the wheel than having to deal with getting a "free wheel" from somewhere else (lots and lots of legal issues). We've even written a rudimentary IoC container because we couldn't use Castle.

    Thanks again for contacting me about this! I love what AutoMapper can do and at any other client, I'd probably be able to use it.

  • Hi Patrick! I wrote today posting about object to object mapping that may be interesting to you: http://weblogs.asp.net/gunnarpeipman/archive/2010/02/07/writing-object-to-object-mapper-first-implementations.aspx You can find highly optimized copying code. If you like it then feel free to use it. :)

Comments have been disabled for this content.