1

Given an XElement input:

<?xml version="1.0"?>
<Item Number="100" ItemName="TestName1" ItemId="1"/>

with an Item model like:

public class Item
{
    public int ItemId { get; set; }
    public string ItemName { get; set; }
    public int? Number { get; set; }
    // public DateTime? Created {get; set;}
}

Why does this code:

    public static T DeserializeObject<T>(XElement element)  where T : class, new()
    {
        try
        {
            var serializer = new XmlSerializer(typeof(T));
            var x = (T)serializer.Deserialize(element.CreateReader());

            return x;
        }
        catch 
        {
            return default(T);
        }
    }

Returns an Item model with default values: ItemId=0, ItemName=null, Number=null instead of the correct values.

This can be fixed by adding attributes to the model [XmlAttribute("ItemName")] but I do not want to require XmlAttributes.

The addition of a nullable DateTime field to the Item model also causes a deserialization exception, even if it has an XmlAttribute.

I have the equivalent code in JSON.net where all I am doing is p.ToObject() on the JToken to deserialize it to an Item object. Is there another technique or deserializer that handles this better without having to qualify with attributes, etc. and that handles nullable values. It should be that easy for the XML version too.

Please consider this question carefully, as I had another similar question closed as [Duplicate] where none of the answers actually covered what I was asking.

WillC
  • 1,761
  • 17
  • 37

4 Answers4

1

It seems if you don't decorate your C# class properties with the XML attributes, the deserializer assumes the properties are elements and not attributes.

string xml = "<Item Number=\"100\" ItemName=\"TestName1\" ItemId=\"1\"><Number>999</Number></Item>";
XElement el = XElement.Parse(xml);
var obj = DeserializeObject<Item>(el); // Returns Number equal to 999 while others are left null or 0
zx485
  • 28,498
  • 28
  • 50
  • 59
Mitch Stewart
  • 1,253
  • 10
  • 12
1

You can try Cinchoo ETL - an open source lib for your needs.

string xml = @"<?xml version=""1.0""?>
    <Item Number = ""100"" ItemName = ""TestName1"" ItemId = ""1"" />";

XDocument doc = XDocument.Parse(xml);

var item = ChoXmlReader<Item>.LoadXElements(new XElement[] { doc.Root }).FirstOrDefault();
Console.WriteLine($"ItemId: {item.ItemId}");
Console.WriteLine($"ItemName: {item.ItemName}");
Console.WriteLine($"Number: {item.Number}");
Console.WriteLine($"Created: {item.Created}");

Hope it helps.

Disclaimer: I'm the author of this library.

Cinchoo
  • 6,088
  • 2
  • 19
  • 34
0

I ended up writing the custom deserializer below that is similar to the json deserializer method for jToken. It should work for basic, flat objects that have simple type properties like string, int, datetime, etc. and the nullable versions of those types. It does not require XmlAttributes.

public static T ToOject<T>(this XElement element) where T : class, new()
{
    try
    {
        T instance = new T();
        foreach (var property in typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance))
        {
            var xattribute = element.Attribute(property.Name);
            var xelement = element.Element(property.Name);
            var propertyType = Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType;
            var value = xattribute?.Value ?? xelement.Value;

            try
            {
                if (value != null)
                {
                    if (property.CanWrite)
                    {
                        property.SetValue(instance, Convert.ChangeType(value, propertyType));
                    }
                }
            }
            catch // (Exception ex) // If Error let the value remain default for that property type
            {
                Console.WriteLine("Not able to parse value " + value + " for type '" + property.PropertyType + "' for property " + property.Name);
            }
        }
        return instance;
    }
    catch (Exception ex)
    {
        return default(T);
    }
}

When you know what is, you can write the following:

   var list = xdocument.Descendants("Item")
        .Select(p => p => p.ToOject<T>())
        .ToList();
WillC
  • 1,761
  • 17
  • 37
0

I had a similar problem with an object consisting of around 30 nested objects. Most of the properties were to be serialized as attributes. I did use XmlAttributeOverrides, but the code is simple enough. Basically I get all the properties from all the classes in a a specific namespace, leave only simple type properties (value type and strings) and then filter out all the properties that already have a Serialization attribute. Then I apply the XmlAttribute to all these properties. In my case there will be no struct types so that's not a problem, but you might have to filter out structs too.

    //Note that the serializer is created just once in the application; this is to prevent 
    //a known memory leak with the XmlSerializer when using 
    //the constructor that takes XmlAttributeOverrides
    private static XmlSerializer serializer = new XmlSerializer(typeof(MyClass), GetAttributeOverrides());

    public static MyClass GetObject(string xml)
    {
        using (var reader = new StringReader(xml))
        {
            return (MyClass)serializer.Deserialize(reader);
        }
    }

    public static XmlAttributeOverrides GetAttributeOverrides()
    {
        var overrides = new XmlAttributeOverrides();

        var simpleProperties = (from t in Assembly.GetExecutingAssembly().GetTypes()
                                where t.IsClass && t.Namespace == typeof(MyClass).Namespace
                                from p in t.GetProperties()
                                where IsSimpleProperty(p)
                                select p);

        foreach (var prop in simpleProperties)
        {
            var attrs = new XmlAttributes() { XmlAttribute = new XmlAttributeAttribute() };
            overrides.Add(prop.DeclaringType, prop.Name, attrs);
        }

        return overrides;
    }

    public static bool IsSimpleProperty(PropertyInfo prop)
    {
        return (prop.PropertyType.IsValueType || prop.PropertyType == typeof(string))
                && !prop.CustomAttributes.Any(a => a.AttributeType.Namespace == "System.Xml.Serialization");
    }
Mike
  • 753
  • 1
  • 7
  • 16