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

A Better XElement to Object Without XMLAttributes in C#

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

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

这可以通过向模型添加属性 [XmlAttribute("ItemName")] 来解决,但我不想要求 XmlAttributes。

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

我在 JSON.net 中有等效代码,我所做的只是 JToken 上的 p.ToObject() 以将其反序列化为 Item 对象。是否有另一种技术或反序列化器可以更好地处理这个问题,而不必使用属性等进行限定,并且可以处理可为空的值。 XML 版本也应该如此简单。

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

似乎如果您不使用 XML 属性修饰 C# class 属性,反序列化程序会假定这些属性是元素而不是属性。

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

您可以尝试 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}");

希望对您有所帮助。

免责声明:我是这个库的作者。

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

我遇到了一个由大约 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");
    }