【问题标题】:XML Serialization similar to what Json.Net can do类似于 Json.Net 可以做的 XML 序列化
【发布时间】:2016-09-01 11:00:20
【问题描述】:

我有以下控制台应用程序:

using System;
using System.IO;
using System.Xml.Serialization;
using Newtonsoft.Json;

namespace OutputApp
{

    public class Foo
    {
        public object Value1 { get; set; }
        public string Value2 { get; set; }
    }

    public class Bar
    {
        public int Arg1 { get; set; }
        public double Arg2 { get; set; }
    }

    class Program
    {
        public static Foo CreateFooBar()
        {
            return new Foo
            {
                Value1 = new Bar
                {
                    Arg1 = 123,
                    Arg2 = 99.9
                },
                Value2 = "Test"
            };
        }

        public static string SerializeXml(object obj)
        {
            using (var stream = new MemoryStream())
            {
                using (var reader = new StreamReader(stream))
                {
                    var serializer = new XmlSerializer(obj.GetType());
                    serializer.Serialize(stream, obj);
                    stream.Position = 0;
                    return reader.ReadToEnd();
                }
            }
        }

        static void Main(string[] args)
        {
            var fooBar = CreateFooBar();

            // Using Newtonsoft.Json

            var json = JsonConvert.SerializeObject(fooBar, Formatting.Indented);
            var xnode = JsonConvert.DeserializeXNode(json, "RootElement");
            var xml = xnode.ToString();

            // Using XmlSerializer, throws InvalidOperationException

            var badXml = SerializeXml(fooBar);

            Console.ReadLine();
        }
    }
}

我有两节课。 Foo 类和 Bar 类。 Foo 类具有 object 类型的属性。这是一个要求,因为它是一个可以保存各种对象的合同,因此我不能将属性设置为具体类型或泛型。

现在我使用CreateFooBar() 方法组成一个虚拟的fooBar 对象。之后,我首先将它序列化为 JSON,它与 Json.Net 完美结合。 然后我使用 Json.Net 的 XML 转换器方法将 json 字符串转换为 XNode 对象。它也很好用。

两者的输出如下:

{
  "Value1": {
    "Arg1": 123,
    "Arg2": 99.9
  },
  "Value2": "Test"
}

<RootElement>
  <Value1>
    <Arg1>123</Arg1>
    <Arg2>99.9</Arg2>
  </Value1>
  <Value2>Test</Value2>
</RootElement>

虽然这可行,但肯定不是很好,因为我必须序列化为 json,然后才能将其序列化为 xml。我想直接序列化成xml。

当我使用 XmlSerializer 执行此操作时,我得到了臭名昭著的 InvalidOperationExceptoin,因为我没有使用 XmlInclude 属性装饰我的类或使用 other workarounds 之一。

无效操作异常

OutputApp.Bar 类型不是预期的。使用 XmlInclude 或 SoapInclude 属性指定静态未知的类型。

XmlSerializer 的任何变通方法都不是一个好的解决方案恕我直言,我认为不需要它,因为在没有 crappy 属性的情况下将对象序列化为 XML 是完全可行的。

有没有人知道 .NET 中有一个好的 Xml 序列化程序可以做到这一点,或者是否有计划将此功能添加到 Json.Net?

有什么想法吗?

更新1

我不反对使用属性,但它需要有意义。我不喜欢XmlInclude 属性的地方是它迫使我陷入循环依赖。假设我有定义基类的程序集 A 和实现派生类的程序集 B。现在 XmlInclude 属性的工作方式是,我必须使用程序集 B 中的子类的类型名称来装饰程序集 A 中的基类。这意味着我有一个循环依赖并且不可行!

更新2

我要澄清的是,我不是在寻找一种解决方案来重构我的控制台应用程序以使其与XmlSerializer 一起工作,我正在寻找一种对我拥有的内容进行 XML 序列化的方法。

下面有一条评论提到使用object 作为数据类型是糟糕的设计。不管这是否属实,这完全是另一回事。关键是它没有理由不能序列化为 XML,我很想找到这样的解决方案。

我个人认为创建“标记”界面是一种肮脏的设计。它滥用接口来解决单个 .NET 类 (XmlSerializer) 的缺陷。如果我将序列化库换成其他东西,那么整个标记界面将是多余的混乱。我不想将我的类耦合到一个序列化程序。

我正在寻找一个优雅的解决方案(如果有的话)?

【问题讨论】:

  • 既然你反对“蹩脚的属性”,听起来你的思想可能是封闭的。您是否仍然愿意尝试任何解决方案,即使在您看来并不完美?
  • 当然,但它必须有意义。我不喜欢属性的是它迫使我陷入循环依赖。假设我有定义基类的程序集 A 和实现派生类的程序集 B。现在 XmlInclude 属性的工作方式是我必须使用程序集 B 中的子类的类型名称来装饰程序集 A 中的基类。繁荣,循环依赖。这是不行的!
  • 如果您使用的是 XmlSerializer,那么任何类型的循环依赖都应该没有问题。再简单不过了:)

标签: c# json xml serialization json.net


【解决方案1】:

您不需要使用 XmlInclude 属性污染您的模型。您可以向XmlSerializer's constructor 明确指出所有已知类:

var serializer = new XmlSerializer(obj.GetType(), new[] { typeof(Bar) });

还使用object 作为基类似乎是一种蹩脚的 方法。至少定义一个标记接口:

public interface IMarker
{
}

您的Bar 将实施:

public class Bar : IMarker
{
    public int Arg1 { get; set; }
    public double Arg2 { get; set; }
}

然后将 Foo 类的 Value1 属性专门用于此标记,而不是使其成为宇宙中最通用的类​​型(不可能):

public class Foo
{
    public IMarker Value1 { get; set; }
    public string Value2 { get; set; }
}

Coz 现在it's pretty trivial 可以在运行时在所有引用的程序集中获取所有加载的类型,这些程序集正在实现标记接口并将它们传递给 XmlSerializer 构造函数:

var type = typeof(IMarker);
var types = AppDomain.CurrentDomain.GetAssemblies()
    .SelectMany(s => s.GetTypes())
    .Where(p != type)
    .Where(p => type.IsAssignableFrom(p))
    .ToArray();

var serializer = new XmlSerializer(obj.GetType(), types);

现在您已经有了一个非常强大的XmlSerializer,它将知道如何正确序列化所有实现您的标记接口的类型。您已经实现了与 JSON.NET 几乎相同的功能。并且不要忘记这个 XmlSerializaer 实例应该驻留在您的 Composition Root project 中,它知道所有加载的类型。

再次使用object 是一个糟糕的设计决策。

【讨论】:

  • 感谢您的意见,但这不是我想要的。请阅读我上面的更新。
  • 如果您认为在您的模型中使用object 是一个比正确的OOP 设计更优雅的解决方案,那么,恐怕我无法进一步帮助您。我所能做的就是祝你在XmlSerializer 课堂上好运:-)。如果您不想使用标记接口,至少您不知道所有可以传递给Value1 的静态类型吗?
  • 1) 同样,这不是关于对象的讨论,而是关于序列化的。 2)我没有说使用对象比其他任何东西更好或更差。我只想知道如何序列化那里的内容。你不知道为什么这可能是相关的完整故事,所以请停止试图说服我任何不属于这个 SO 问题的事情。
  • 不,我当然不知道完整的故事。如果您期望任何相关答案,您有责任在此处提供。由于您尚未这样做,因此我为可能偶然发现此问题的其他人提供了一种可能的解决方法。我并不是想用我的回答来说服你。在阅读我的回答后,您现在应该确信的是,您无法使用框架中内置的 XmlSerializer 类来实现您正在寻找的东西。它为您提供了我在回答中概述的一些解决方法。
  • 当然,如果您不需要将它内置到框架中,这里有一个 XML 序列化器,您可以签出:sharpserializer.com/en/index.html。它也可用作 NuGet:Install-Package sharpserializer。使用此 XML 序列化程序,您无需显式指定所有已知类型。示例用法:new Polenter.Serialization.SharpSerializer().Serialize(fooBar, Console.OpenStandardOutput());.
猜你喜欢
  • 1970-01-01
  • 2019-10-20
  • 2011-12-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-14
  • 1970-01-01
相关资源
最近更新 更多