Object to Object Mapper, Part 1

1/27/2010

Ok, so the title is really a lie but I'll get into that later. Anyway, I mentioned a while back in the LMS project that due to an issue that popped up I had a side project that had reared its head. In the LMS I have multiple DLLs floating around. Each one of them is self contained. For example if I drop a Netflix service into the directory, it should pick it up and go. That means finding the service, loading it up, etc. Well in order to do this, it means that these services may need to use the database to save information (specifically config info). This is pretty simple as all it took was a slight adjustment in my ORM to accept multiple assemblies. The issue came about in letting the user modify the information. In the Netflix example, one of the things that you need is an app ID. The user, in this example, would need to enter his own ID. So we have a Netflix object with an AppID field that gets saved to the database. However the interface has no clue about this object (no idea what it is, what the fields are, etc.).

Now in a normal system this would be easy as we just have the Netflix service creator come up with a page/form to modify the info. In the LMS, this would be an issue as it's suppose to be designed such that the interface doesn't matter. It may be a web page or a form (or verbal or whatever). So we end up with an interesting issue where we need to copy data to and from the interface and object without knowing what we are copying. Well my first instinct was to look online to see if anyone had something similar when I came across AutoMapper. I like AutoMapper as it does an efficient job of copying data from one object to another. However in my case I don't know what the source is when I know what the destination should be and I when I know what the destination should be I don't know what the source is... On top of that the interface may actually be multiple objects of different types. Maybe I want to display strings inside a textbox and booleans as a checkbox. In AutoMapper it seems like the idea is to copy one object (including child objects) to a distinct destination object. In my case I want to copy a single object and potentially cut it up into dozens of smaller objects without knowing in advance what those objects are or really how I'm going to cut it up (or join it back together again for that matter). So unless there is a feature that I'm missing, AutoMapper was out. So I decided to do what I normally do, start from scratch.

Below I'm showing you the first attempt at the issue:

   1: internal class DestinationInfo
   2: {
   3:     public string Name { get; set; }
   4:     public Type Type { get; set; }
   5: }
   6:  
   7: public class Mapping<Source>
   8: {
   9:     private Type SourceType { get; set; }
  10:     private Dictionary<string, DestinationInfo> MappingDictionary = new Dictionary<string, DestinationInfo>();
  11:  
  12:     public Mapping()
  13:     {
  14:         SourceType = typeof(Source);
  15:     }
  16:  
  17:     public void Map<Destination>(Expression<Func<Source, object>> SourceExpression,
  18:         Expression<Func<Destination, object>> DestinationExpression)
  19:     {
  20:         string SourceName = GetName<Source>(SourceExpression);
  21:         string DestinationName = GetName<Destination>(DestinationExpression);
  22:         Type DestinationType = typeof(Destination);
  23:         DestinationInfo DestinationInfo = new DestinationInfo();
  24:         DestinationInfo.Name = DestinationName;
  25:         DestinationInfo.Type = DestinationType;
  26:         MappingDictionary.Add(SourceName, DestinationInfo);
  27:     }
  28:  
  29:     public void Map(Expression<Func<Source, object>> SourceExpression,TypeMapping Mapping)
  30:     {
  31:         string SourceName = GetName<Source>(SourceExpression);
  32:         Type SourceType = typeof(Source);
  33:         Type SourcePropertyType = SourceType.GetProperty(SourceName).PropertyType;
  34:         Type DestinationType=null;
  35:         string DestinationName="";
  36:         if (Mapping.Properties.ContainsKey(SourcePropertyType))
  37:         {
  38:             DestinationType = Mapping.Properties[SourcePropertyType].Type;
  39:             DestinationName = Mapping.Properties[SourcePropertyType].Name;
  40:         }
  41:         DestinationInfo DestinationInfo = new DestinationInfo();
  42:         DestinationInfo.Name = DestinationName;
  43:         DestinationInfo.Type = DestinationType;
  44:         MappingDictionary.Add(SourceName, DestinationInfo);
  45:     }
  46:  
  47:     public object CreateObject(string SourcePropertyName,object SourceObject)
  48:     {
  49:         object ReturnObject = null;
  50:         if (MappingDictionary.ContainsKey(SourcePropertyName))
  51:         {
  52:             Type TypeUsing=MappingDictionary[SourcePropertyName].Type;
  53:             Assembly TempAssembly = TypeUsing.Assembly;
  54:             ReturnObject = TempAssembly.CreateInstance(TypeUsing.FullName);
  55:             object SourcePropertyValue=SourceType.GetProperty(SourcePropertyName).GetValue(SourceObject,null);
  56:             Type DestinationPropertyValueType = TypeUsing.GetProperty(MappingDictionary[SourcePropertyName].Name).PropertyType;
  57:             if (DestinationPropertyValueType == typeof(string) && SourcePropertyValue != null)
  58:             {
  59:                 SourcePropertyValue = SourcePropertyValue.ToString();
  60:             }
  61:             TypeUsing.GetProperty(MappingDictionary[SourcePropertyName].Name).SetValue(ReturnObject, SourcePropertyValue, null);
  62:         }
  63:         return ReturnObject;
  64:     }
  65:  
  66:     public object CreateObject(Expression<Func<Source, object>> SourceExpression, object SourceObject)
  67:     {
  68:         string SourcePropertyName = GetName<Source>(SourceExpression);
  69:         return CreateObject(SourcePropertyName, SourceObject);
  70:     }
  71:  
  72:     public List<object> CreateObject(object SourceObject)
  73:     {
  74:         List<object> ReturnList = new List<object>();
  75:         foreach (string Key in MappingDictionary.Keys)
  76:         {
  77:             object TempObj = CreateObject(Key, SourceObject);
  78:             if (TempObj != null)
  79:             {
  80:                 ReturnList.Add(TempObj);
  81:             }
  82:         }
  83:         return ReturnList;
  84:     }
  85:  
  86:     public void Sync(string SourcePropertyName,object SourceObject, object DestinationObject)
  87:     {
  88:         string DestinationName=MappingDictionary[SourcePropertyName].Name;
  89:         Type DestinationType = MappingDictionary[SourcePropertyName].Type;
  90:         Type DestinationPropertyType = DestinationType.GetProperty(DestinationName).PropertyType;
  91:         Type SourcePropertyType=SourceType.GetProperty(SourcePropertyName).PropertyType;
  92:         object DestinationPropertyValue=DestinationType.GetProperty(DestinationName).GetValue(DestinationObject,null);
  93:         if (DestinationPropertyType == typeof(string) && SourcePropertyType != typeof(string))
  94:         {
  95:             DestinationPropertyValue = Global.Parse((string)DestinationPropertyValue, SourcePropertyType);
  96:         }
  97:         SourceType.GetProperty(SourcePropertyName).SetValue(SourceObject, DestinationPropertyValue, null);
  98:     }
  99:  
 100:     public void Sync(Expression<Func<Source, object>> SourceExpression, object SourceObject, object DestinationObject)
 101:     {
 102:         string SourcePropertyName = GetName<Source>(SourceExpression);
 103:         Sync(SourcePropertyName, SourceObject, DestinationObject);
 104:     }
 105:  
 106:     public void Sync(object SourceObject, List<object> DestinationObject)
 107:     {
 108:         int x=0;
 109:         foreach (string Key in MappingDictionary.Keys)
 110:         {
 111:             PropertyInfo Property = SourceType.GetProperty(Key);
 112:             Sync(Property.Name, SourceObject, DestinationObject[x]);
 113:             ++x;
 114:         }
 115:     }
 116:  
 117:     private string GetName<T>(Expression<Func<T, object>> expression)
 118:     {
 119:         string Name = "";
 120:         if (expression.Body.NodeType == ExpressionType.Convert)
 121:         {
 122:             Name = expression.Body.ToString().Replace("Convert(", "").Replace(")", "");
 123:             string[] Splitter = { "." };
 124:             string[] SplitName = Name.Split(Splitter, StringSplitOptions.None);
 125:             Name = SplitName[SplitName.Length - 1];
 126:         }
 127:         else
 128:         {
 129:             Name = expression.Body.ToString();
 130:             string[] Splitter = { "." };
 131:             string[] SplitName = Name.Split(Splitter, StringSplitOptions.None);
 132:             Name = SplitName[SplitName.Length - 1];
 133:         }
 134:         return Name;
 135:     }
 136: }

I liked the idea of the mapping object from my ORM (which I took from Fluent NHibernate). It's simple and allows for the separation of code in nice ways. In this case, this is a mapping object for the source object. The class has a couple of Map functions. Each of them take in an Expression object for the source and one takes in an Expression object for the destination while the other takes in a TypeMapping object. In the first one, the idea is that you know when you're mapping what you want it to map to (TextBox, etc.) and the properties that you want to map (Netflix's AppID to the TextBox's Text property for example). However in my case, I don't know what they are at the same time so the second version of Map comes into play. In that case we send in the TypeMapping object. The TypeMapping object can be found below:

   1: internal class DestinationPropertyInfo
   2: {
   3:     public string Name { get; set; }
   4:     public Type Type { get; set; }
   5: }
   6:  
   7: public class TypeMapping
   8: {
   9:     public TypeMapping()
  10:     {
  11:     }
  12:  
  13:     public void Map<Source, Destination>(string DestinationPropertyName)
  14:     {
  15:         DestinationPropertyInfo Temp = new DestinationPropertyInfo();
  16:         Temp.Name = DestinationPropertyName;
  17:         Temp.Type = typeof(Destination);
  18:         Properties.Add(typeof(Source), Temp);
  19:     }
  20:  
  21:     public void Map<Source, Destination>(Expression<Func<Destination, object>> DestinationExpression)
  22:     {
  23:         Map<Source, Destination>(GetName<Destination>(DestinationExpression));
  24:     }
  25:  
  26:     private string GetName<T>(Expression<Func<T, object>> expression)
  27:     {
  28:         string Name = "";
  29:         if (expression.Body.NodeType == ExpressionType.Convert)
  30:         {
  31:             Name = expression.Body.ToString().Replace("Convert(", "").Replace(")", "");
  32:             string[] Splitter = { "." };
  33:             string[] SplitName = Name.Split(Splitter, StringSplitOptions.None);
  34:             Name = SplitName[SplitName.Length - 1];
  35:         }
  36:         else
  37:         {
  38:             Name = expression.Body.ToString();
  39:             string[] Splitter = { "." };
  40:             string[] SplitName = Name.Split(Splitter, StringSplitOptions.None);
  41:             Name = SplitName[SplitName.Length - 1];
  42:         }
  43:         return Name;
  44:     }
  45:  
  46:     internal Dictionary<Type, DestinationPropertyInfo> Properties = new Dictionary<Type, DestinationPropertyInfo>();
  47: }

The TypeMapping object is very similar to the Mapping object as it takes its cue from my ORM's ClassMapping object. This object takes a type and maps that to a destination type/property. So for instance you can tell it to map strings to a TextBox's Text property. It then keeps this list for use in the Mapping object. Doing it this way allows me to keep the information in two separate places. The TypeMapping class would be defined in the interface and the Mapping class would be in the separate Netflix service DLL. But we could tell it whatever we wanted without having to worry about what the type is.

Getting back to the Mapping class, the next set of functions are CreateObject. With the mapping functions we know that if property A is type X then it maps to type Y's property B (in other words we know what the hell we're doing at this point). The issue that I ran into is the fact that the interface doesn't know how many properties there are, what they are, etc. In other words we need the Mapping object to give us the items, which is where the CreateObject functions come into play. In two of the instances we're dealing with individual properties (given a source object and the property you want it gives you the output object). But to be honest, we really want all of the properties spit out back to us in a list (since the interface doesn't know what they are anyway), which is where the third function comes into play. All it does is uses the mapping to create a new object and populate the property that it's told to with the value of the property.

The last set of functions are the Sync functions. Basically these do the reverse of the CreateObject functions. They take the individual objects that were created and set the properties of the original object with the appropriate values. Most of it isn't that difficult but it does call a function that I haven't shown yet:

   1: public static class Global
   2: {
   3:     public static object Parse(string Input, Type OutputType)
   4:     {
   5:         if (string.IsNullOrEmpty(Input))
   6:             return null;
   7:         Type[] MethodInputType=new Type[1];
   8:         MethodInputType[0]=typeof(string);
   9:         MethodInfo ParseMethod = OutputType.GetMethod("Parse", MethodInputType);
  10:         object Item=OutputType.Assembly.CreateInstance(OutputType.FullName);
  11:         object[] Values=new object[1];
  12:         Values[0]=Input;
  13:         return ParseMethod.Invoke(Item, Values);
  14:     }
  15: }

All that function does is finds the Parse function of the object type and calls it. So of course this limits it to things like integers, floats, etc. basically things with a Parse function. But that's all there is to it. Two classes, one that the DLL sets up and one that the interface sets up, and we're good to go. Well almost. Like I said, this was my first pass at it but there are a couple flaws (no sub classes, lack of type conversion, doesn't even bother with lists of objects, etc.). And this isn't really an object to object mapper. Perhaps an object to object ( and another object, and another object) mapper (would that be an OTOAAOAAOM?)? Object to List of Objects Mapper (OTLOOM)? A config interface generator? An object generator? I have no freakin' clue what to call this and if someone else has an idea I'd love to hear it. Anyway, look it over, leave feedback, and happy coding.



Comments