在C#中没有XMLAttributes的更好的XElement对象

时间:2018-01-06 19:16:28

标签: c# xml linq-to-xml json-deserialization

给出一个XElement输入:

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

使用项目模型,如:

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

为什么这段代码:

    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);
        }
    }

返回具有默认值的Item模型:ItemId = 0,ItemName = null,Number = null而不是正确的值。

这可以通过向模型添加属性[XmlAttribute(&#34; ItemName&#34;)]来修复,但我不想要XmlAttributes。

向Item模型添加可为空的DateTime字段也会导致反序列化异常,即使它具有XmlAttribute也是如此。

我在JSON.net中有相同的代码,其中我正在做的是JToken上的p.ToObject()以将其反序列化为Item对象。是否有另一种技术或反序列化器可以更好地处理这个问题,而无需使用属性等来处理它并处理可以为空的值。对于XML版本来说应该也很容易。

请仔细考虑这个问题,因为我有另一个类似的问题关闭[重复],其中没有一个答案实际涵盖了我的要求。

4 个答案:

答案 0 :(得分:1)

似乎如果不使用XML属性修饰C#类属性,则反序列化器假定属性是元素而不是属性。

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

答案 1 :(得分:1)

您可以尝试Cinchoo ETL - 一个满足您需求的开源库。

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}");

希望它有所帮助。

免责声明:我是这个图书馆的作者。

答案 2 :(得分:0)

我最终编写了下面的自定义反序列化器,类似于jToken的json反序列化器方法。它应该适用于具有简单类型属性(如string,int,datetime等)的基本扁平对象以及这些类型的可空版本。它不需要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);
    }
}

当您知道什么是,您可以写下以下内容:

   var list = xdocument.Descendants("Item")
        .Select(p => p => p.ToOject<T>())
        .ToList();

答案 3 :(得分:0)

我对一个由大约30个嵌套对象组成的对象也遇到了类似的问题。大多数属性将被序列化为属性。我确实使用了XmlAttributeOverrides,但是代码很简单。基本上,我从特定命名空间中所有类的所有属性中获得所有属性,只保留简单的类型属性(值类型和字符串),然后滤除所有已具有序列化属性的属性。然后,我将XmlAttribute应用于所有这些属性。就我而言,将没有结构类型,所以这不是问题,但是您可能也必须过滤掉结构。

    //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");
    }