XML序列化与反序列化接口对接实战,看这篇就够了

关键字:c# .NET XML 序列化 反序列化

本文为接口对接实践经验分享,不对具体的XML概念定义进行阐述;涉及工具类及处理方法已在生产环境使用多年,可放心使用。当然如果你发现问题,或有不同想法,也非常欢迎指出讨论。
以系统对接为基础,本文力求达到:

  • 工具类直接使用;
  • XML处理示例全覆盖;
  • 让有XML格式需求的朋友看这篇就够了;

JSON同样作为接口数据格式最常用选择,计划另写一篇。敬请关注

0、工具类

点击查看 XmlParser 工具类
public static class XmlParser<T>
{
	private static XmlSerializer fXmlSerializer = null;
	private static UTF8Encoding fUtf8 = new UTF8Encoding();

	/// <summary>
	/// 带头信息;无命名空间
	/// </summary>
	/// <param name="item"></param>
	/// <returns></returns>
	public static string ToXml(T item)
	{
		if (fXmlSerializer == null)
		{
			fXmlSerializer = new XmlSerializer(typeof(T));
		}

		using (var ms = new MemoryStream())
		{
			//XmlSerializer不给Encoding,其XML宣告会是UTF-16 ;而且不能直接用Encoding.UTF8
			var xmlWriter = new XmlTextWriter(ms, fUtf8);

			XmlSerializerNamespaces xsnp = new XmlSerializerNamespaces();
			xsnp.Add(string.Empty, string.Empty);

			fXmlSerializer.Serialize(xmlWriter, item, xsnp);
			string rst = Encoding.UTF8.GetString(ms.ToArray());

			return rst;
		}
	}

	/// <summary>
	/// 获取Xml段;不带Xml头
	/// </summary>
	/// <param name="item"></param>
	/// <returns></returns>
	public static string ToXmlRemoveHead(T item)
	{
		var result = ToXml(item);
		if (!string.IsNullOrEmpty(result))
		{
			var firtPos = result.IndexOf("?>");
			if (firtPos > -1)
			{
				result = result.Remove(0, firtPos + 2);
			}
		}
		return result;
	}

	public static T FromXml(string str)
	{
		if (fXmlSerializer == null)
		{
			fXmlSerializer = new XmlSerializer(typeof(T));
		}

		using (XmlReader reader = new XmlTextReader(new StringReader(str)))
		{
			return (T)fXmlSerializer.Deserialize(reader);
		}
	}
}

1、常规XML示例

<?xml version="1.0" encoding="utf-8" ?>
<root>
  <title>标题</title> 
  <items>
    <item>
      <code>C001</code>
      <name>张三</name>
    </item>
    <item>
      <code>C002</code>
      <name>李四</name>
    </item>
  </items>
  <details>
    <detail key="tel" value="123" />
    <detail key="mobile" value="456" />
  </details>
</root>

较为常用的XML中含有:

  • XML元素: <title>标题</title>
  • XML属性: key="tel"
  • XML子节点:元素子节点<item>及属性子节点<detail>

一般一个接口有多个子节点也只用一种子节点类型,此为示例列出两种

1.1、实体类定义

VS提供了工具
VS选择性粘贴
xml粘贴为代码
左边为用VS粘贴生成的代码,我喜欢手动改为右边的定义。

1.2、实体类示例

[XmlRoot("root")]
public partial class SampleRoot
{
    [XmlElement("title")]
    public string Title { get; set; }
    
    
    [XmlArray("items"), XmlArrayItem("item")]
    public RootItem[] items { get; set; }
    
    [XmlArray("details"), XmlArrayItem("detail")]
    public RootDetail[] Details { get; set; }
    
}

[XmlRoot("item")]
public class RootItem
{
    [XmlElement("code")]
    public string Code { get; set; }
    
    [XmlElement("name")]
    public string Name { get; set; }
}

[XmlRoot("detail")]
public class RootDetail
{
    [XmlAttribute("key")]
    public string Key { get; set; }
    
    [XmlAttribute("value")]
    public string Value { get; set; }
}

1.3、XML与实体类互转,即序列化与反序列化

用上面提供的工具类,非常方便:

//读出xml
var xml = File.ReadAllText("Sample.xml");

//反序列化,转成实体
var entity = XmlParser<SampleRoot>.FromXml(xml);

//序列化,转成字符串
var xml2 = XmlParser<SampleRoot>.ToXml(entity);

image
调试看出,XML转成对象之后再转成XML,得到当初一样的XML内容。

该工具类在反序列化读取XML时能自动读出CDATA节点内容,在序列化时对于需要转义的字符也会进行转义。产生的字符串是格式良好能通过验证的有效XML。

在大多数正常的系统对接中,通常上面的定义可以解决碰到的大多数对接需求;

如果是新接口定义,也建议有条件的话按这种子节点层次分明没有过多的格式要求来定义结构实现业务。子节点只用元素或只用属性,各就各位,所谓元素属性子节点,简单规范好理解。

当然现实中不可避免,会有一些其他的需求。

2、进一步的需求

2.1、节点有属性有子节点;举例code名字也相同

<?xml version="1.0" encoding="utf-8" ?>
<root>
	<title>标题</title>
	<items>
		<item code="0">
			<code>C001</code>
			<name>张三</name>
		</item>
		<item code="1">
			<code>C002</code>
			<name>李四</name>
		</item>
	</items>
</root>
2.2.1、对应实体
[XmlRoot("root")]
public partial class SampleRoot
{
    [XmlElement("title")]
    public string Title { get; set; }


    [XmlArray("items"), XmlArrayItem("item")]
    public RootItem[] items { get; set; } 
}

[XmlRoot("item")]
public class RootItem
{
    //属性code
    [XmlAttribute("code")]
    public string Index { get; set; }
    //元素code
    [XmlElement("code")]
    public string Code { get; set; }

    [XmlElement("name")]
    public string Name { get; set; }
} 

2.2、父节点有属性,子节点有属性且有元素值

<?xml version="1.0" encoding="utf-8" ?>
<root>
	<title>标题</title> 
	<details name="明细">
		<detail key="tel">张三</detail>
		<detail key="mobile">李四</detail>
	</details>
</root>
2.2.1、对应实体
[XmlRoot("root")]
public class SampleRoot
{
    [XmlElement("title")]
    public string Title { get; set; }

    [XmlElement("details")]
    public Details Details { get; set; }
}

public class Details
{
    [XmlAttribute("name")]
    public string Name { get; set; }

    [XmlElement("detail")]
    public Detail[] DetailList { get; set; }
}

public class Detail
{
    [XmlAttribute("key")]
    public string Key { get; set; }

    [XmlText()]
    public string Value { get; set; }//可以叫Value,或其他任何名字
}

3、特殊需求

3.1、序列化要输出CDATA节点

工具类读取XML为实体时自动支持CDATA节点,输出成XML要加上CDATA要这样定义

[XmlRoot("item")]
public partial class RootItem
{
    [XmlAttribute("code")]
    public string Index { get; set; }

    //[XmlElement("code")]
    [XmlIgnore]
    public string Code { get; set; }

    //[XmlElement("value")]
    [XmlIgnore]
    public int Value { get; set; }
}

public partial class RootItem
{
    [XmlElement("code")]
    public XmlNode _Code
    {
        get
        {
            var node = new XmlDocument().CreateNode(XmlNodeType.CDATA, "", "");
            node.InnerText = Code;
            return node;
        }
        set { Code = value.InnerText; }
    }
    [XmlElement("value")]
    public XmlNode _Value
    {
        get
        {
            var node = new XmlDocument().CreateNode(XmlNodeType.Text, "", "");
            node.InnerText = Value.ToString();
            return node;
        }
        set { Value = Convert.ToInt32(value.InnerText); }
    }
}
3.1.1、应用效果

image
VS格式化后对比

 XML格式检查,格式化显示;最好用的工具依然是宇宙最强IDE-VS
image

3.2、不区分大小写

所谓不区分大小写,主要是指反序列化时,对XML元素/属性不区分大小写的转成指定实体类。

严格的节点不区分大小写,没有实现;网上有个方案说先将XML字符串做一次全部转小写(或大写)这样节点固定了就能读出来;如果业务数据也允许不分区大小写,是个办法。但是对业务数据做了假设,不太好。

根据碰到的情况,主要是节点首字母不区分大小写(有的产品用的是大驼峰:UserName,有的产品用的是小驼峰:userName),如下方法取巧。

示例XML,Code/Name和code/name都要支持

<?xml version="1.0" encoding="utf-8" ?>
<root>
	<title>标题</title>
	<items>
		<item code="0">
			<code>C001</code>
			<name>张三</name>
		</item>
		<item code="1">
			<Code>C002</Code>
			<Name>李四</Name>
		</item>
	</items>
</root>

这种情况如下定义实体类解决;

大小驼峰各定义一次,使用的地方用任意一个即可(实体类定义修改,使用不修改)

[XmlRoot("item")]
public partial class RootItem
{
    [XmlAttribute("code")]
    public string Index { get; set; }

    [XmlElement("code")]
    public string Code { get; set; }

    [XmlElement("Code")]
    public string _Code { get { return Code; }  set { Code = value; } }

    [XmlElement("name")]
    public string Name { get; set; }

    [XmlElement("Name")]
    public string _Name { get { return Name; } set { Name = value; } }
}

使用的地方原先用Code/Name,继续用;_Code/_Name只用于XML解释。

同样如果再有产品大小驼峰都不用,用蛇式(user_name)或烤肉串式(user-name)命名法,类似再多定义下。规避不能完全支持节点不区分大小写的问题。

4、扩展不展

XML格式在文件存储中也很常用,Word文档可以用XML存储,PowerDesigner的PDM文件也是XML格式,解释这类复杂的XML可能要用到xslt,比如是否能支持完全不区分大小写的XML读取,这块用的很少没有实际接触,不展开/展不开。

5、平台杂谈

阿里的奇门接口主要支持XML格式;在与各大厂平台对接中对阿里系对接很有好感;他们先做的业务其他平台可以直接参考(最推荐是直接抄^-^),技术提供的SDK也是真实有效,很多其他大厂要不没有SDK,要不SDK连编译都通不过。也许是我们接的太快了,或者是他们项目外包了。

奇门SDK上对XML的处理类,好像写的“复杂”了些,你看出来了吗?
平台杂谈

感谢你看到这里,希望对你有帮助