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的處理類,好像寫的「複雜」了些,你看出來了嗎?
平台雜談

感謝你看到這裡,希望對你有幫助