【问题标题】:C#: Render object as XMLC#:将对象呈现为 XML
【发布时间】:2009-02-23 18:27:03
【问题描述】:

我正在寻找一种将对象树转换为 XML 的方法。写起来会很有趣,但我相信有人已经写过了。这是我的愿望清单:

  • 它不应该关心构造函数
  • 它应该理想地处理循环引用(不要太在意如何)
  • 它不应该要求对对象进行更改 - 例如,没有自定义属性
  • 它不应该关心或要求已知类型(例如,XmlInclude)
  • XML 应该非常简单 - 它需要运营团队的成员可读
  • 如果一个属性不能被序列化,它应该只是抑制错误并继续
  • 可以处理列表和字典

我不需要重建对象模型,所以只写解决方案就可以了(可能是预期的)。

我认为打折:

  • XmlSerializer - 需要无参数构造函数,不支持循环引用
  • DataContractSerializer - 需要属性(选择加入)

【问题讨论】:

  • 该死的好问题! :P
  • 有趣的问题。恐怕“它不应该关心构造函数”的要求可能无法满足。如果我的类型有 10 个不同的构造函数,序列化程序如何知道使用哪个构造函数?
  • @siz - 我应该澄清一下。它不在乎,因为它只是序列化(对象到 XML)而从不反序列化(XML 到对象)。这就是我所说的“只写”解决方案的意思。
  • 你为什么要“排除”XmlSerializer,因为它比你需要的更强大?这是否意味着您将拒绝使用 Windows,因为它可以进行语音识别,或者您将拒绝使用 RDBMS,因为它现在可以进行地理空间?看来自己写这篇文章很忙。
  • @cheeso 我排除了它,因为它不适合我的场景。我从第三方库中获得了一个对象。 XmlSerializer 无法处理它(循环引用、需要参数的构造函数等)。如果您可以让序列化程序处理上述需求,我很乐意看到它

标签: c# xml serialization


【解决方案1】:

Robert Rossney 的帖子让我觉得这可能比我想象的要少。所以这是一个非常粗略的尝试。它处理以下内容:

  • 如果无法读取属性,则会将异常打印为值
  • 循环引用和多次出现。它将一个 ID 与每个元素相关联;如果一个元素出现两次,它只是指向 ref ID。 Ref ID 对于对象图是唯一的(我可能应该使用 GUID,但这适合我的目的)。
  • 派生类型没有问题
  • 它不需要属性或特定的构造函数或其他废话
  • 它可以处理只读属性

这是一个输出示例(在我的测试对象中,订单上的“货币”产品引发异常)。

<Customer Ref="1">
  <FirstName>Paul</FirstName>
  <LastName>Stovell</LastName>
  <FullName>Paul Stovell</FullName>
  <Orders>
    <Order Ref="2">
      <SKU>Apples</SKU>
      <Price>27.30</Price>
      <Currency>Something bad happened</Currency>
      <Customer Ref="1" />
    </Order>
    <Order Ref="3">
      <SKU>Pears</SKU>
      <Price>17.85</Price>
      <Currency>Something bad happened</Currency>
      <Customer Ref="1" />
    </Order>
    <Order Ref="2" />
  </Orders>
</Customer>

这是示例对象模型和用法:

static void Main(string[] args)
{
    var customer = new Customer();
    customer.FirstName = "Paul";
    customer.LastName = "Stovell";
    customer.Orders.Add(new Order(customer) { Price = 27.30M, SKU = "Apples"});
    customer.Orders.Add(new Order(customer) { Price = 17.85M, SKU = "Pears"});
    customer.Orders.Add(customer.Orders[0]);

    var output = new StringWriter();
    var writer = new XmlTextWriter(output);
    writer.Formatting = Formatting.Indented;
    WriteComplexObject("Customer", customer, writer);
    Console.WriteLine(output.ToString());
    Console.ReadKey();
}

class Customer
{
    private readonly List<Order> _orders = new List<Order>();

    public Customer()
    {
    }

    public string FirstName { get; set; }
    public string LastName { get; set; }

    public string FullName
    {
        // Read-only property test
        get { return FirstName + " " + LastName; }
    }

    public List<Order> Orders
    {
        // Collections test
        get { return _orders; }
    }
}

class Order
{
    private readonly Customer _customer;

    public Order(Customer customer)
    {
        _customer = customer;
    }

    public string SKU { get; set; }
    public decimal Price { get; set; }
    public string Currency
    {
        // A proprty that, for some reason, can't be read
        get
        {
            throw new Exception("Something bad happened");
        }
    }

    public Customer Customer
    {
        get { return _customer; }
    }
}

这是实现:

public static void WriteObject(string name, object target, XmlWriter writer)
{
    WriteObject(name, target, writer, new List<object>(), 0, 10, -1);
}

private static void WriteObject(string name, object target, XmlWriter writer, List<object> recurringObjects, int depth, int maxDepth, int maxListLength)
{
    var formatted = TryToFormatPropertyValueAsString(target);
    if (formatted != null)
    {
        WriteSimpleProperty(name, formatted, writer);
    }
    else if (target is IEnumerable)
    {
        WriteCollectionProperty(name, (IEnumerable)target, writer, depth, maxDepth, recurringObjects, maxListLength);
    }
    else
    {
        WriteComplexObject(name, target, writer, recurringObjects, depth, maxDepth, maxListLength);
    }
}

private static void WriteComplexObject(string name, object target, XmlWriter writer, List<object> recurringObjects, int depth, int maxDepth, int maxListLength)
{
    if (target == null || depth >= maxDepth) return;
    if (recurringObjects.Contains(target))
    {
        writer.WriteStartElement(name);
        writer.WriteAttributeString("Ref", (recurringObjects.IndexOf(target) + 1).ToString());
        writer.WriteEndElement();
        return;
    }
    recurringObjects.Add(target);

    writer.WriteStartElement(name);
    writer.WriteAttributeString("Ref", (recurringObjects.IndexOf(target) + 1).ToString());
    foreach (var property in target.GetType().GetProperties())
    {
        var propertyValue = ReadPropertyValue(target, property);
        WriteObject(property.Name, propertyValue, writer, recurringObjects, depth + 1, maxDepth, maxListLength);
    }
    writer.WriteEndElement();
}

private static object ReadPropertyValue(object target, PropertyInfo property)
{
    try { return property.GetValue(target, null); }
    catch (Exception ex) { return ReadExceptionMessage(ex); }
}

private static string ReadExceptionMessage(Exception ex)
{
    if (ex is TargetInvocationException && ex.InnerException != null)
        return ReadExceptionMessage(ex.InnerException);
    return ex.Message;
}

private static string TryToFormatPropertyValueAsString(object propertyValue)
{
    var formattedPropertyValue = null as string;
    if (propertyValue == null)
    {
        formattedPropertyValue = string.Empty;
    }
    else if (propertyValue is string || propertyValue is IFormattable || propertyValue.GetType().IsPrimitive)
    {
        formattedPropertyValue = propertyValue.ToString();
    }
    return formattedPropertyValue;
}

private static void WriteSimpleProperty(string name, string formattedPropertyValue, XmlWriter writer)
{
    writer.WriteStartElement(name);
    writer.WriteValue(formattedPropertyValue);
    writer.WriteEndElement();
}

private static void WriteCollectionProperty(string name, IEnumerable collection, XmlWriter writer, int depth, int maxDepth, List<object> recurringObjects, int maxListLength)
{
    writer.WriteStartElement(name);
    var enumerator = null as IEnumerator;
    try
    {
        enumerator = collection.GetEnumerator();
        for (var i = 0; enumerator.MoveNext() && (i < maxListLength || maxListLength == -1); i++)
        {
            if (enumerator.Current == null) continue;
            WriteComplexObject(enumerator.Current.GetType().Name, enumerator.Current, writer, recurringObjects, depth + 1, maxDepth, maxListLength);
        }
    }
    catch (Exception ex)
    {
        writer.WriteElementString(ex.GetType().Name, ReadExceptionMessage(ex));
    }
    finally
    {
        var disposable = enumerator as IDisposable;
        if (disposable != null)
        {
            disposable.Dispose();
        }
        writer.WriteEndElement();
    }
}

我仍然想知道是否有更多经过验证的解决方案。

【讨论】:

    【解决方案2】:

    这似乎使用反射来编写很简单:给定一个对象实例,使用其类名创建一个 XML 元素,然后遍历其所有属性。

    为每个属性创建一个具有其名称的元素:

    • 如果是值类型,则将其文本设置为其值的 XML Schema 文本;
    • 如果它实现了IEnumerable,则遍历它并为每个项目创建一个元素;
    • 如果它是任何其他引用类型,请将元素的内容设置为属性的 XML 表示。

    使用包含您已序列化的每个对象的哈希码的 HashSet 跟踪循环/多次引用;如果你在 HashSet 中找到了一个对象的哈希码,那么你已经序列化了它。 (如果发生这种情况,我不知道您想在 XML 中添加什么。)

    但是不,我没有任何代码可以做到这一点。

    【讨论】:

      【解决方案3】:

      我怀疑您是否会找到在所有课程中都特别有效的东西。正如您所指出的,XmlSerializer 是微软迄今为止在通用方面所做的最大努力。

      另一端是visualizers,它们是特定类所独有的。我认为目前还没有多少快乐的媒介。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多