5
\$\begingroup\$

So I have the below (which is working correctly and all my unit tests are passing) but it's ugly and is violating SOLID, I will have to change the class every time I want to be able to map a different type (The multiple if statements).

Code

/// <summary>
/// Maps the matching properties from one type to the other by name.
/// <para>If the name does not match then it will not be mapped.</para>
/// <para>This class was intended to be used in a class that derives from "IValueInjection" </para>
/// </summary>
/// <param name="target">The target object.</param>
/// <param name="source">The source object.</param>
/// <exception cref="System.InvalidCastException"> If there is no cast type then this will be thrown</exception>
public static void MapMatchingProperties(object target, object source)
{
    var sourceProps = source.GetType().GetProperties();
    var targetProps = target.GetType().GetProperties();

    for (var i = 0; i < targetProps.Count(); i++)
    {
        var sourceProperty = sourceProps.FirstOrDefault(x => String.Equals(x.Name, targetProps[i].Name, StringComparison.InvariantCultureIgnoreCase));
        if (sourceProperty != null)
        {
            var objVal = GetPropValue(source, sourceProperty.Name);
            if(objVal == null ) continue;

            var fullProperty = target.GetType().GetProperty(targetProps[i].Name);
            var newType = fullProperty.PropertyType;
            object castType = null;

            if (newType == typeof(int))
            {
                castType = (int)objVal;
            }
            else if (newType == typeof(bool))
            {
                castType = (bool)objVal;
            }
            else if (newType == typeof(string))
            {
                castType = (string)objVal;
            }
            else if (newType == typeof(long))
            {
                castType = (long)objVal;
            }
            else if (newType == typeof(DateTime))
            {
                castType = objVal as DateTime? ?? new DateTime();
            }

            if (castType == null)
            {
                throw new InvalidCastException(string.Format("Could not find the correct cast type for property: {0}, type: {1}", targetProps[i].Name, fullProperty.PropertyType));
            }

            var val = castType;
            SetPropValue(target, val, targetProps[i].Name);
        }
    }
}

private static object GetPropValue(object src, string propName)
{
    return src.GetType().GetProperty(propName).GetValue(src, null);
}

private static void SetPropValue(object target, object value, string propName)
{
    var prop = target.GetType().GetProperty(propName);
    prop.SetValue(target, value);
}

As you can see what I am doing is finding the matching property by property name and then setting it.

The reason for this is basically to map JSON classes (classes with their property names are all lower case) to my own classes.

The main problem is the if {} else {} statements since I will have to expand that when I want to map a new type.

An example of this class used in a mapping scenario:

    protected override void Inject(LogResult source, object target)
    {
        MappingHelper.MapMatchingProperties(target, source);

        var dateTime = target.GetType().GetProperty("Time", BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
        if (dateTime == null) return;

        var convertedTime = source.Time.UnixTimeStampToDateTime(); // Converts to a valid DateTime
        if (convertedTime == default(DateTime)) return;

        dateTime.SetValue(target, convertedTime);
    }

Log Result

public class LogResult
{
    public int ID { get; set; } <-- This property name has different casing
    public string Kind { get; set; }
    public int Time { get; set; }
    public string Text { get; set; }
}

View Model

public class LogViewModel
{
    public int Id { get; set; }
    public string Kind { get; set; }
    public DateTime Time { get; set; }
    public string Text { get; set; }
}
\$\endgroup\$
  • 1
    \$\begingroup\$ Is this theoretical code or practical? I ask because my first thought was to suggest using JSON.Net for parsing, and it handles type conversions. That suggestion isn't helpful if you're doing this for learning/practice purposes. \$\endgroup\$ – Rich Seviora Dec 21 '15 at 15:27
  • \$\begingroup\$ It's a bit of both really. I'll take a look at JSON.Net but the question is really me learning how to use reflection correctly. \$\endgroup\$ – Jamie Rees Dec 21 '15 at 15:28
  • \$\begingroup\$ I've added my answer... did you run into any problems at first that led you down this path? \$\endgroup\$ – Rich Seviora Dec 21 '15 at 15:42
  • \$\begingroup\$ The problem was I wanted to use a mapping framework (in this case ValueInjecter) to map my JSON classes to my POCO's, ViewModels \$\endgroup\$ – Jamie Rees Dec 21 '15 at 15:43
5
\$\begingroup\$

You should not bother casting it. As you're calling SetPropValue which accepts any object of type Object, it should not be necessary to cast it to a specific type.

\$\endgroup\$
  • \$\begingroup\$ I just did this an my unit tests broke. There is a test case where the JSON class contains a property int, and the class to map it to has the same property but it is a DateTime. So when I attempt to set the value we get an ArgumentException as we cannot convert Int to a DateTime \$\endgroup\$ – Jamie Rees Dec 21 '15 at 15:52
  • \$\begingroup\$ IMHO then your solution is in some way incomplete. The reason your existing code passes is that it is safely casting to DateTime. I suspect if you tried to assign an DateTime to a int property it would give you an invalid cast exception. So at first pass that sounds like an incomplete test suite. I think the more important question is what is the design expectation for conversions? If you get an invalid type, what value should you be assigning? \$\endgroup\$ – Rich Seviora Dec 21 '15 at 16:03
  • \$\begingroup\$ I think I have a 'mixed' design here. I think I will revisit the implementation. Thanks. \$\endgroup\$ – Jamie Rees Dec 21 '15 at 16:08
  • \$\begingroup\$ Actually thinking about it, if it cannot map the property (because it is the wrong type) and it's a reference type then it should set it to default(t) \$\endgroup\$ – Jamie Rees Dec 21 '15 at 16:13
  • \$\begingroup\$ How specific do you want to get? You can either require explicit matching (probably not a great idea), or assignability. If the latter, use Type.IsAssignableFrom. \$\endgroup\$ – Rich Seviora Dec 21 '15 at 18:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.