C# XML 序列化器

Json序列化工具可选择的很多, 但XML序列化器可选择的不多, 总体推荐 ExtendedXmlSerializer. 微软官方的 DataContractSerializer 尤其不太好用, 时不时在 xml element 上莫名其妙增加一些namespace信息.

ExtendedXmlSerializer

支持.Net Framework和 core, 同时部分兼容 XmlSerializer XML Tag Attribute, 支持 List/Dictionary 集合属性的序列化。
https://extendedxmlserializer.github.io/
https://github.com/ExtendedXmlSerializer/home/wiki/The-Basics

DataContractSerializer

微软官方
支持 私有字段 的序列化。
支持 List/Dictionary 属性序列化
老的 WCF 官方使用的序列化器
https://learn.microsoft.com/en-us/dotnet/fundamentals/runtime-libraries/system-runtime-serialization-datacontractserializer
https://www.albahari.com/nutshell/DataContractSerializer.pdf

XmlSerializer

微软官方
不支持 List/Dictionary 属性序列化,
不支持 私有字段 的序列化。
https://peterdaugaardrasmussen.com/2017/09/20/differences-between-datacontractserializer-and-xmlserializer/

SharpSerializer

需要一个比 DataContractSerializer 更简单、更快速的 XML 序列化库。
支持 私有字段 的序列化。
https://github.com/polenter/SharpSerializer
https://www.sharpserializer.net/en/tutorial/index.html

XSerializer

需要支持 加密、压缩、多态、接口支持。
需要比 DataContractSerializer 更强的序列化能力。
https://github.com/bfriesen/XSerializer

ExtendedXmlSerializer 使用代码示例

ExtendedXmlSerializer 默认会将类中所有public的属性和字段做序列化, 除非为public属性打上 [XmlIgnore] 标签.

IExtendedXmlSerializer 提供了两种输出控制模式:

  1. ClassicMode: Classic模式下List或Dictionary属性不输出容器的 Capacity 等属性, 但同时也会干扰 null 属性的强制输出
  2. 非ClassicMode: null属性的强制输出可以很完美, 但会自动输出List或Dictionary容器的 Capacity 等属性

推荐使用非ClassicMode, 因为在XML去除 Capacity tag 非常简单. 下面是一个代码片段

        string xml22 = @"<Root><Capacity>100</Capacity><a>sssssss<Capacity/></a></Root>";
        var xDoc=  XDocument.Parse(xml22);
        xDoc.Descendants("Capacity").Remove();
        Console.WriteLine(xDoc.ToString());

ExtendedXmlSerializer示例代码:

using ExtendedXmlSerializer;
using ExtendedXmlSerializer.Configuration;
using ExtendedXmlSerializer.ContentModel.Content;
using ExtendedXmlSerializer.ExtensionModel.Xml;
using ExtendedXmlSerializer.ReflectionModel;
using System.Linq.Expressions;
using System.Xml;
using System.Xml.Serialization;


using ExtendedXmlSerializer;
using ExtendedXmlSerializer.Configuration;
using ExtendedXmlSerializer.ContentModel.Content;
using ExtendedXmlSerializer.ExtensionModel.Xml;
using ExtendedXmlSerializer.ReflectionModel;
using System.Linq.Expressions;
using System.Xml;
using System.Xml.Serialization;


[XmlRoot("DemoRoot")] // 根节点重命名
class Demo
{

    [XmlIgnore]  //序列化时忽略指定的属性
    public String? ignoredField { get; set; }

    [Verbatim]   //输出的文字嵌入到  CDATA 中
    public string? cdataField { get; set; }

    [XmlElement("renamedField")]  //重命名 xml tag 
    public string? renamedField { get; set; }
    public string? nullField { get; set; }
    public int intField { get; set; }
    public Int32 intField2 { get; set; }
    public List<String> strList { get; set; } = new List<string>();

    public string getMessage()
    {
        return "Hello";
    }


    /// <summary>
    /// 在这里设置强制需要输出XML的字段, 以便将这些信息在初始化IExtendedXmlSerializer时告知 Serializer 
    /// </summary>
    /// <returns></returns>
    public static List<Expression<Func<Demo, string>>> GetForceOutputMembers()
    {
        var memberExpressions = new List<Expression<Func<Demo, string>>>();
        memberExpressions.Add(x => x.nullField);
        memberExpressions.Add(x => x.renamedField);
        return memberExpressions;
    }
}

[XmlRoot("DemoRoot")] // 根节点重命名
class RestoredDemo
{
    public int intField { get; set; }
    public Int32 intField233 { get; set; }

    public List<String> strList { get; set; } = new List<string>();
}


public class Address
{
    public string location { get; set; }
}

public class Person
{
    public List<string> Hobbies { get; set; } // List<string> 属性
    public List<Address> addresses { get; set; }
}

public class MyClass
{
    public string Name { get; set; }
    public int? Age { get; set; }
}


class Test
{
    public static void Main()
    {
        BasicTest();
        //ListElementTest();
    }


    public static void BasicTest()
    {
        Expression<Func<Demo, string>> forceOutputMember = x => x.nullField;
        //Expression<Func<Demo, string>> forceOutputMember= Demo.GetForceOutputMembers()[0];

        //创建 IExtendedXmlSerializer 的配置类
        var config = new ConfigurationContainer()
         .EnableImplicitTypingByInspecting<Demo>() //在反序列化时一定要调用这个方法, 否则大概率会因创建的对象和指定的类不一致而抛异常
         .UseOptimizedNamespaces() //输出更简洁的XML namespace写法
         .EnableClassicMode() //Classic模式下List或Dictionary属性不输出容器的 Capacity 等属性, 但同时也会干扰 null 属性的强制输出
         //.Emit(EmitBehaviors.Always) //强制所有属性输出至XML, 即使它们的取值为null, 注意: Classic模式下不生效
         .WithUnknownContent().Continue() //增加XML反序列的容错性
         .EnableImplicitTyping(typeof(Demo), typeof(List<>)) //序列化到XML时不输出指定C#对象命名空间
         .Type<Demo>()  //返回目标类的 ITypeConfiguration, 信息, 以便完成根节点/子节点重命名等后序链式调用
         .Name("DemoRoot")  //根节点重命名, 推荐使用  XmlRoot Attribute
         //.Member(x => x.renamedField).Name("renamedField") //内部节点重命名,   推荐使用 XmlElement Attribute
         //.Member(x => x.nullField).EmitWhen(x => true) // 如果某个字段值为null, 可以强制序列化到XML, 注意: Classic模式下仍能生效
         .Member(forceOutputMember).EmitWhen(x => true) // 如果某个字段值为null, 可以强制序列化到XML, 注意: Classic模式下仍能生效
        ;

        //  使用遍历的方式无法强制将null属性输出到XML, 不管是否在Classic模式下
        foreach (var property in typeof(MyClass).GetProperties())
        {
            //config.Member(x => x.nullField).EmitWhen(x => true); //Member()如使用Lambda 参数, 可以强制将null属性输出到XML
            config.Member(property).EmitWhen(x => true); //Member()如使用 property 参数, 无法强制将null属性输出到XML, 不管是否在Classic模式下, 看起来是个bug
        }


        IExtendedXmlSerializer extendedXmlSerializer = config.Create();
        Demo demo = new Demo();
        demo.intField = 100;
        demo.intField2 = 200;
        demo.ignoredField = "ignoredField";
        demo.cdataField = "cdataField";
        demo.renamedField = null;//"renamedField";
        demo.nullField = null; //当取值为 null 时候将不会输出到 xml中, 所以需要初始化成空字符串
        demo.strList.Add("item1");
        demo.strList.Add("item2");

        //init XmlWriterSettings
        var settings = new System.Xml.XmlWriterSettings();
        settings.Indent = true;
        settings.OmitXmlDeclaration = true; //省略 xml declare 头

        //序列化
        string xml = extendedXmlSerializer.Serialize(settings, demo);
        xml = xml.Replace("xsi:nil=\"true\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"", "");
        xml = xml.Replace("  />", "/>");
        Console.WriteLine(xml);


        IExtendedXmlSerializer extendedXmlSerializer2 = new ConfigurationContainer()
            .EnableImplicitTypingByInspecting<Demo>() //在反序列化时一定要调用这个方法, 否则大概率会因创建的对象和指定的类不一致而抛异常  
            .UseOptimizedNamespaces()
            .WithUnknownContent().Continue() //增加XML反序列的容错性
            .EnableImplicitTyping(typeof(Demo)) //序列化到XML时不输出指定C#对象命名空间
            .Create();
        var demo2 = extendedXmlSerializer2.Deserialize<Demo>(xml);
        Console.WriteLine(demo2.intField);
        Console.WriteLine(demo2.strList[0]);
        if (demo2.nullField == null)
        {
            Console.WriteLine("nullField is null");
        }
        else
        {
            Console.WriteLine($"nullField:{demo2.nullField}.");
        }
    }

    public static void ListElementTest()
    {
        //测试List成员序列化
        var person = new Person
        {
            Hobbies = new List<string> { "Reading", "Gaming", "Coding" },
            addresses = new List<Address> { new Address() { location = "SH" }, new Address() { location = "BJ" } }
        };
        // 创建序列化器
        var serializer = new ConfigurationContainer()
            .UseOptimizedNamespaces()
            .WithUnknownContent().Continue() //增加XML反序列的容错性
            .EnableImplicitTyping(typeof(Person), typeof(Address)) // 注册类型
            .EnableClassicMode() //Classic模式下List或Dictionary属性不输出容器的 Capacity 等属性, 但同时也会干扰 null 属性的强制输出
            .Create();


        //init XmlWriterSettings
        var settings = new System.Xml.XmlWriterSettings();
        settings.Indent = true;
        settings.OmitXmlDeclaration = true; //省略 xml declare 头

        // 序列化为 XML
        string xml3 = serializer.Serialize(settings, person);
        Console.WriteLine(xml3);

        var person2 = serializer.Deserialize<Person>(xml3);
        Console.WriteLine(person2.Hobbies[0]);
        Console.WriteLine(person2.addresses[1].location);
    }
}
posted @ 2025-03-18 21:30  harrychinese  阅读(94)  评论(0)    收藏  举报