user3010406
user3010406

Reputation: 579

How to automatically map the values between instances of two different classes if both have same properties?

I have two classes with exactly same members (properties and fields) with same datatypes. I want to map the members from one to other in automated way. I know there are more practical means development-wise to handle. One simple solution is to manually map each member from one instance to other. But, I want to automate this as some general solution.

Assuming you have the following code:

public MyObject1 AssignTest (MyObject1 obj1, MyObject2 obj2)
{
    //Code here for auto map
}

Where MyObject1 and MyObject2 have the exact same properties with same datatype. I do not want to go through and assign the values individually (i.e. MyObject1.Property1 = MyObject2.Property1 etc.). Is it possible to assign all the values that have been specified in MyObject1 to MyObject2 automatically?

Upvotes: 10

Views: 20511

Answers (8)

Vinod Srivastav
Vinod Srivastav

Reputation: 4253

Using Reflection you can read the fields & properties of an object instance and can copy it over to another.

Like for a Mapper<From,To> you need to provide the object instance with values as From then use (To)Activator.CreateInstance(typeof(To)) to create a new instance of To and can copy over the fields & properties from <From> to <To>.

Mapper Class

The following is code is just trying to do the same. It comes with a Constructor and 3 methods which empowers it to Auto or Manually Map the fields between two classes.

public class Mapper<From,To>
{
    private Dictionary<string,string> maptable;
    private To returnObject;
    private Type typeFrom;
    private Type typeTo;

    public Mapper()
    {
        maptable = new Dictionary<string,string>();
        returnObject = (To)Activator.CreateInstance(typeof(To));
        typeFrom = typeof(From);
        typeTo = typeof(To);
    }
    
    public Mapper<From,To> Map(string from,string to)
    {
        maptable.Add(from,to);
        return this;
    }
    
    public To ConvertTo(From from)
    {
        foreach (var from_field in typeFrom.GetFields())
        {
            if(maptable.Keys.Contains(from_field.Name))
            {
                var toFieldName = maptable[from_field.Name];
                var fieldOfB = typeTo.GetField(toFieldName);
                fieldOfB.SetValue(returnObject, from_field.GetValue(from));
            }
        }
        
        foreach (var from_property in typeFrom.GetProperties())
        {
            if(maptable.Keys.Contains(from_property.Name))
            {
                var toPropertyName = maptable[from_property.Name];
                var propertyOfB = typeTo.GetProperty(toPropertyName);
                propertyOfB.SetValue(returnObject, from_property.GetValue(from));
            }
        }

        return (To)returnObject;
    }


    public To AutoConvertTo<F, T>(From from)
    {
        foreach (var fieldOfA in typeFrom.GetFields())
        {
            var fieldOfB = typeTo.GetField(fieldOfA.Name);
            fieldOfB.SetValue(returnObject, fieldOfA.GetValue(from));
        }

        foreach (var fromProperty in typeFrom.GetProperties())
        {
            var propertyOfB = typeTo.GetProperty(fromProperty.Name);
            propertyOfB.SetValue(returnObject, fromProperty.GetValue(from));
        }

        return (To)returnObject;
    }
}

Example Class A & B

To explain the code we need two example class:

public class A
{
    public string Name { get; set; }
    public int Count;
    public string first {get;set;}
    public string last {get;set;}
    public int Age;
}

public class B
{
    public string Name { get; set; }
    public int Count;
    public string first {get;set;}
    public string last {get;set;}
    public int Age;
    public string ExclusiveB {get;set;}
}

Example

So if we create an instance of A we can convert it to B as in the example below.

// Instance of class A with data
var a = new A
    {
        Name = "a",
        Count = 1,
        first = "first",
        last="last",
        Age= 100
    };
    
    a.Dump("a"); //Dump is the function to display the output in Linqpad much like `Console.Write` 

    //Here we create an instance of the Mapper class
    var m = new Mapper<A,B>();

    // Then we are manually mapping the fields from A to B using dictionary
    // Please note the datatype must match 
    m.Map("first","last");
    m.Map("last","first");
    m.Map("Age","Count");
    m.Map("Count","Age");
    m.Map("Name","Name");
    
    //here we execute the manual mapping to convert `a` to `b2`
    var manualB2 = m.ConvertTo(a); 
    
    manualB2.Dump("ManualMap"); //Display in console 
    
    //This is auto mapping the fields 
    var autoB = m.AutoConvertTo<A, B>(a);
    autoB.Dump("AutoMap");

So we have AutoMapping where the fields/properties are cloned & copied as per their matching name and there is ManualMap which is created using the Map<From,To>() method so you can map the fields/properties as you like.

Output

If everything works well you can see the out as

enter image description here


Upvotes: 0

GKG4
GKG4

Reputation: 501

The question is quite old but using JsonConvert makes it faster?

MyObject2 obj2 = JsonConvert.DeserializeObject<MyObject2>(JsonConvert.SerializeObject(obj1));

Upvotes: 2

Avtandil Kavrelishvili
Avtandil Kavrelishvili

Reputation: 1757

I improved and generalized @keenthinker response, It always works any type of object.

Call to static method and pass map classes and object

var result = SimpleMapper.Map<FromModel, ToModel>(data);

Implimentation:

public static class SimpleMapper
{
    public static T Map<F, T>(F from)
    {
        // inicialize return object
        T b = (T)Activator.CreateInstance(typeof(T));

        // copy fields
        Type typeOfA = typeof(F);
        Type typeOfB = typeof(T);
        foreach (var fieldOfA in typeOfA.GetFields())
        {
            var fieldOfB = typeOfB.GetField(fieldOfA.Name);
            fieldOfB.SetValue(b, fieldOfA.GetValue(from));
        }
        // copy properties
        foreach (var propertyOfA in typeOfA.GetProperties())
        {
            var propertyOfB = typeOfB.GetProperty(propertyOfA.Name);
            propertyOfB.SetValue(b, propertyOfA.GetValue(from));
        }

        return (T)b;
    }
}

Upvotes: 2

Amit Joshi
Amit Joshi

Reputation: 16387

Extending from accepted answer from @pasty, I have created generic method for this purpose.

public static TDest MapSourceToDest<TSource, TDest>(TSource source)
                                    where TSource : class//We are not creating an instance of source, no need to restrict parameterless constructor
                                    where TDest : class, new()//We are creating an instance of destination, parameterless constructor is needed
{
    if(source == null)
        return null;

    TDest destination = new TDest();

    var typeOfSource = source.GetType();
    var typeOfDestination = destination.GetType();

    foreach(var fieldOfSource in typeOfSource.GetFields())
    {
        var fieldOfDestination = typeOfDestination.GetField(fieldOfSource.Name);
        if(fieldOfDestination != null)
        {
            try
            { fieldOfDestination.SetValue(destination, fieldOfSource.GetValue(source)); }
            catch(ArgumentException) { }//If datatype is mismatch, skip the mapping
        }
    }

    foreach(var propertyOfSource in typeOfSource.GetProperties())
    {
        var propertyOfDestination = typeOfDestination.GetProperty(propertyOfSource.Name);
        if(propertyOfDestination != null)
        {
            try
            { propertyOfDestination.SetValue(destination, propertyOfSource.GetValue(source)); }
            catch(ArgumentException) { }//If datatype is mismatch, skip the mapping
        }
    }

    return destination;
}

One may need to alter the filters on generic types; but everything else will work cross any types. Null check is added for fieldOfDestination and propertyOfDestination just in case a member is missing; this adds up little more flexibility.

Upvotes: 2

keenthinker
keenthinker

Reputation: 7830

One possibility to make this (for example for the purpose of creating your own automapper or understand how it basically works) would be to use (as already suggested) Reflection. The code can look like this:

// TODO: error handling
// Test classes
public class A
{
    public string Name { get; set; }
    public int Count;
}

public class B
{
    public string Name { get; set; }
    public int Count;
}
// copy routine
public B CopyAToB(A a)
{
    B b = new B();
    // copy fields
    var typeOfA = a.GetType();
    var typeOfB = b.GetType();
    foreach (var fieldOfA in typeOfA.GetFields())
    {
        var fieldOfB = typeOfB.GetField(fieldOfA.Name);
        fieldOfB.SetValue(b, fieldOfA.GetValue(a));
    }
    // copy properties
    foreach (var propertyOfA in typeOfA.GetProperties())
    {
        var propertyOfB = typeOfB.GetProperty(propertyOfA.Name);
        propertyOfB.SetValue(b, propertyOfA.GetValue(a));
    }

    return b;
}

The function can be used like this:

var a = new A
{
    Name = "a",
    Count = 1
};

var b = CopyAToB(a);
Console.Out.WriteLine(string.Format("{0} - {1}", b.Name, b.Count));

The output is:

a - 1

Please note, that the usage of reflection comes with a price - it costs performance. Using reflection you can access both private and public object members. This is for example used from Visual Studio to create test accessor objects in order to access all test object members.

Please have a look at the existing automappers (see the other answers for links) and use them instead of reinventing the wheel by yourself - the existing libraries are optimized for speed, thoroughly tested and are very comfortable to use. This way you will minimize errors in your code.

Upvotes: 11

Brook
Brook

Reputation: 6019

Mapping libraries such as ValueInjector or AutoMapper are a great help for exactly this sort of functionality.

Using AutoMapper you would create a mapping using something like this

Mapper.CreateMap<MyObject1,MyObject2>();

It has a number of default conventions, one of which is that by default it will copy properties with identical types/names.

And then actually do a mapping like this

var myObject2 = Mapper.Map<MyObject1,MyObject2>(myObject1);

Of course you can do this easy enough with reflection as well, but with libraries such as these someone has put a lot of thought into adding all sorts of handy mapping functionality, as well as done performance tuning. AutoMapper, for example, uses IL generation to read values instead of reflection so it is significantly faster for repeatedly mapping things (very hand for mapping big collections of things)

Upvotes: 8

Doug Marzella
Doug Marzella

Reputation: 19

AutoMapper is another good tool for something like this http://automapper.codeplex.com/

You map from one object to another with something like this:

Mapper.CreateMap<MyClass, MyDTO>();
var myDTO = Mapper.Map<MyClass, MyDTO>(myClass);

Upvotes: 0

drew_w
drew_w

Reputation: 10430

There are a number of tools that do this. Look at the "TranslateTo" routine in service stack as an example. They have excellent auto mapping (https://github.com/ServiceStack/ServiceStack/wiki/Auto-mapping).

Using this, all you would have to do is:

 obj2 = obj1.TranslateTo<MyObject2>();

Simple and elegant!

In case you are interested a few other references to similar topics:

Upvotes: 2

Related Questions