6
\$\begingroup\$

I am working on an application in which I have to parse an XML file. Nodes of the XML file corresponds to an object. I have to populate the properties of the objects at run time. The code below queries the metadata of property and sets its value. Is there a better way to do it or is there a better way to map the elements of an XML to an object?

public static void SetPropertyValue(object obj, string propertyName, object propertyValue)
    {
        if (obj == null || string.IsNullOrWhiteSpace(propertyName))
        {
            return;
        }

        Type objectType = obj.GetType();

        PropertyInfo propertyDetail = GetPropertyInfo(objectType, propertyName);

        if (propertyDetail != null && propertyDetail.CanWrite)
        {
            Type propertyType = propertyDetail.PropertyType;

            Type dataType = propertyType;

            // Check for nullable types
            if (propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
            {
                // Check for null or empty string value.
                if (propertyValue == null || string.IsNullOrWhiteSpace(propertyValue.ToString()))
                {
                    propertyDetail.SetValue(obj, null);
                    return;
                }
                else
                {
                    dataType = propertyType.GetGenericArguments()[0];
                }
            }

            if (dataType.Equals(typeof(int)))
            {
                propertyValue = Convert.ToInt32(propertyValue);
            }

            if (dataType.Equals(typeof(long)))
            {
                propertyValue = Convert.ToInt64(propertyValue);
            }

            if (dataType.Equals(typeof(short)))
            {
                propertyValue = Convert.ToInt16(propertyValue);
            }

            if (dataType.Equals(typeof(bool)))
            {
                string stringPropertyValue = propertyValue.ToString();
                if (stringPropertyValue.Equals(Boolean.FalseString, StringComparison.OrdinalIgnoreCase) || stringPropertyValue.Equals("0"))
                {
                    propertyValue = false;
                }
                else
                {
                    if (stringPropertyValue.Equals(Boolean.TrueString, StringComparison.OrdinalIgnoreCase) || stringPropertyValue.Equals("1"))
                    {
                        propertyValue = true;
                    }
                }
            }

            if (dataType.Equals(typeof(float)))
            {
                propertyValue = Convert.ToSingle(propertyValue);
            }

            if (dataType.Equals(typeof(double)))
            {
                propertyValue = Convert.ToDouble(propertyValue);
            }

            if (dataType.Equals(typeof(decimal)))
            {
                propertyValue = Convert.ToDecimal(propertyValue);
            }

            if (dataType.Equals(typeof(byte)))
            {
                propertyValue = Convert.ToByte(propertyValue);
            }

            if (dataType.Equals(typeof(string)))
            {
                propertyValue = Convert.ToString(propertyValue);
            }

            if (dataType.Equals(typeof(char)))
            {
                propertyValue = Convert.ToChar(propertyValue);
            }

            if (dataType.Equals(typeof(DateTime)))
            {
                propertyValue = DateTime.ParseExact(propertyValue.ToString(), "yyyy-MM-dd", CultureInfo.InvariantCulture);
            }

            propertyDetail.SetValue(obj, propertyValue);

        }
    }
\$\endgroup\$
  • 4
    \$\begingroup\$ In order to tell whether there is a better way of translating your xml into an object we need to see the xml structure and the target object. \$\endgroup\$ – t3chb0t Aug 30 '15 at 11:16
  • 3
    \$\begingroup\$ Have you considered using plain Jane XML serialization? \$\endgroup\$ – RubberDuck Aug 30 '15 at 11:23
  • 3
    \$\begingroup\$ I agree with @RubberDuck , I would attempt to obtain a Schema for this file and use the XSD.exe to generate a class to which the xml could be serialized against. Its direct and no need for reflection. \$\endgroup\$ – Bearcat9425 Aug 31 '15 at 16:22
  • 1
    \$\begingroup\$ Thanks @RubberDuck .. I am trying to upgrade a legacy application (something that is > 20 years old) and the information that I have about the schema is limited. However it makes to create the schema based on the sample xml files that I have and update it as more information is available. \$\endgroup\$ – Sandeep Sep 3 '15 at 14:26
3
\$\begingroup\$

You can use reflection to parse some common .Net types:

public static object Parse(Type type, string str)
{
    try
    {
        var parse = type.GetMethod("Parse", new[] {typeof(string)});
        if (parse == null) throw new NotSupportedException();
        return parse.Invoke(null, new object[] { str });
    }
    //or don't catch
    catch (Exception)
    {
        return null;
    }
}

It will basically allow you to replace this code:

        if (dataType.Equals(typeof(float)))
        {
            propertyValue = Convert.ToSingle(propertyValue);
        }

        if (dataType.Equals(typeof(double)))
        {
            propertyValue = Convert.ToDouble(propertyValue);
        }

        if (dataType.Equals(typeof(decimal)))
        {
            propertyValue = Convert.ToDecimal(propertyValue);
        }

        if (dataType.Equals(typeof(byte)))
        {
            propertyValue = Convert.ToByte(propertyValue);
        }

        if (dataType.Equals(typeof(string)))
        {
            propertyValue = Convert.ToString(propertyValue);
        }

        if (dataType.Equals(typeof(char)))
        {
            propertyValue = Convert.ToChar(propertyValue);
        }

        if (dataType.Equals(typeof(int)))
        {
            propertyValue = Convert.ToInt32(propertyValue);
        }

        if (dataType.Equals(typeof(long)))
        {
            propertyValue = Convert.ToInt64(propertyValue);
        }

        if (dataType.Equals(typeof(short)))
        {
            propertyValue = Convert.ToInt16(propertyValue);
        }

with this line:

propertyValue = Parse(dataType, propertyValue.ToString());

It will work for every other class, that has public static void Parse(string) method declared (most standart value types do). You can add Parse(string) method to your own classes, to make them support this kind of deserialization. You can also modify the above code to use Parse(string, IFormatProvider) instead, to avoid parsing issues related to different decimal separators in different cultures.

\$\endgroup\$
3
\$\begingroup\$

You can use Convert.ChangeType method for removing some conditionals. An example below :

class Person
{
    public int Name {get;set;}
    public bool BoolVar {get;set;}
    public float FloatVar { get; set; }
    public DateTime DateVar {get;set;}
}

class MainClass
{
    public static void Main (string[] args)
    {
        Person p = new Person ();

        SetPropertyValue (p, "BoolVar", true);
        SetPropertyValue (p, "FloatVar", 11.5);
        SetPropertyValue (p, "DateVar", "2015-05-01");

        Console.WriteLine (p.BoolVar);
        Console.WriteLine (p.FloatVar);
        Console.WriteLine (p.DateVar);

    }

    public static void SetPropertyValue(object obj, string propertyName, object propertyValue)
    {
        if (obj == null || string.IsNullOrWhiteSpace(propertyName))
        {
            return;
        }

        Type objectType = obj.GetType();

        PropertyInfo propertyDetail = objectType.GetProperty(propertyName);


        if (propertyDetail != null && propertyDetail.CanWrite)
        {
            Type propertyType = propertyDetail.PropertyType;

            Type dataType = propertyType;

            // Check for nullable types
            if (propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
            {
                // Check for null or empty string value.
                if (propertyValue == null || string.IsNullOrWhiteSpace(propertyValue.ToString()))
                {
                    propertyDetail.SetValue(obj, null);
                    return;
                }
                else
                {
                    dataType = propertyType.GetGenericArguments()[0];
                }
            }

            propertyValue = Convert.ChangeType (propertyValue, propertyType);

            propertyDetail.SetValue(obj, propertyValue);

        }
    }
}

If the XML format is yours and you be able to replace it, it's better to use xml serialization.

\$\endgroup\$
  • \$\begingroup\$ Thanks Niels. Unfortunately I am still trying to fully understand the schema. I had the function Convert.ChangeType in mind but I thought the exception handling associated with this function will become a little messy so I didn't follow this path. \$\endgroup\$ – Sandeep Sep 3 '15 at 14:32

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.