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