適用対象は、.NET Framework 1.1 以降。
目次
検証環境
- LINQPad 7
- .NET 7
XmlSerializer の使い方
サンプルで扱う PurchaseOrder.xml。
<?xml version="1.0"?> <PurchaseOrder PurchaseOrderNumber="99503" OrderDate="1999-10-20"> <Address Type="Shipping"> <Name>Ellen Adams</Name> <Street>123 Maple Street</Street> <City>Mill Valley</City> <State>CA</State> <Zip>10999</Zip> <Country>USA</Country> </Address> <Address Type="Billing"> <Name>Tai Yee</Name> <Street>8 Oak Avenue</Street> <City>Old Town</City> <State>PA</State> <Zip>95819</Zip> <Country>USA</Country> </Address> <DeliveryNotes>Please leave packages in shed by driveway.</DeliveryNotes> <Items> <Item PartNumber="872-AA"> <ProductName>Lawnmower</ProductName> <Quantity>1</Quantity> <USPrice>148.95</USPrice> <Comment>Confirm this is electric</Comment> </Item> <Item PartNumber="926-AA"> <ProductName>Baby Monitor</ProductName> <Quantity>2</Quantity> <USPrice>39.98</USPrice> <ShipDate>1999-05-21</ShipDate> </Item> </Items> </PurchaseOrder>
ファイルの読み書き
デシリアライズ。
var path = Path.Combine(Util.MyQueriesFolder, @"PurchaseOrder.xml"); using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read)) { var serializer = new XmlSerializer(typeof(PurchaseOrder)); var order = (PurchaseOrder)serializer.Deserialize(stream); }
var path = Path.Combine(Util.MyQueriesFolder, @"PurchaseOrder_copy.xml"); using (var stream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.Write)) { var serializer = new XmlSerializer(typeof(PurchaseOrder)); serializer.Serialize(stream, order); }
文字列の読み書き
デシリアライズ。
var xml = @"<?xml version=""1.0""?> <PurchaseOrder PurchaseOrderNumber=""99503"" OrderDate=""1999-10-20""> <Address Type=""Shipping""> <Name>Ellen Adams</Name> <Street>123 Maple Street</Street> <City>Mill Valley</City> <State>CA</State> <Zip>10999</Zip> <Country>USA</Country> </Address> <Address Type=""Billing""> <Name>Tai Yee</Name> <Street>8 Oak Avenue</Street> <City>Old Town</City> <State>PA</State> <Zip>95819</Zip> <Country>USA</Country> </Address> <DeliveryNotes>Please leave packages in shed by driveway.</DeliveryNotes> <Items> <Item PartNumber=""872-AA""> <ProductName>Lawnmower</ProductName> <Quantity>1</Quantity> <USPrice>148.95</USPrice> <Comment>Confirm this is electric</Comment> </Item> <Item PartNumber=""926-AA""> <ProductName>Baby Monitor</ProductName> <Quantity>2</Quantity> <USPrice>39.98</USPrice> <ShipDate>1999-05-21</ShipDate> </Item> </Items> </PurchaseOrder>"; using (var stream = new StringReader(xml)) { var serializer = new XmlSerializer(typeof(PurchaseOrder)); var order = (PurchaseOrder)serializer.Deserialize(stream); }
using (var stream = new StringWriter()) { var serializer = new XmlSerializer(typeof(PurchaseOrder)); serializer.Serialize(stream, order); stream.ToString().Dump(); }
属性
XMLのデータ型。
// タグ名と同じならあってもなくてもOK [XmlRoot] public class Book { // タグ名と同じならあってもなくてもOK [XmlElement] public int Id { get; set; } public string Title { get; set; } // フィールでも出力される public int Price; // タグ名が違う場合は明示的に指定 [XmlElement("Published")] public DateTime published; [XmlAttribute] public string ISBN13 { get; set; } public BookType BookType { get; set; } public BookType BookType { get; set; } public Genre Genre { get; set; } public Publisher Publisher { get; set; } public List<Author> Authors { get; set; } // コレクションのタグ指定 [XmlArray("Languages")] [XmlArrayItem("Language")] public List<string> Langs { get; set;} // コレクションの親要素抜きでベタに並べる場合 [XmlElement("Seller")] public List<Seller> Sellers { get; set;} // Dictionaryは使えない //public Dictionary<string, string> NotSupported { get; set;} } public class Author { public string Name { get; set; } } public class Publisher { public string Name { get; set; } } public class Seller { // <Name>タグで囲わず、<Publisher>タグのテキストとして出力する [XmlText] public string Name { get; set; } } public enum BookType { PaperBook, EBook, } public enum Genre { // enumの出力する値を指定できる [XmlEnum("コミック")] Comic, [XmlEnum("ラノベ")] LightNovel, }
var book = new Book { Id = 1, Title = "薬屋でひとりごっこ", Price = 777, published = new DateTime(2000, 1, 2), Authors = new List<Author> { new Author{ Name = "春夏冬" }, new Author{ Name = "洞爺湖" }, new Author{ Name = "京都三ノ宮" }, }, Publisher = new Publisher { Name = "中学館" }, Langs = new List<string> { "ja", "en" }, Sellers = new List<Seller> { new Seller{ Name = "あまぞん" }, new Seller{ Name = "よどばし" }, }, BookType = BookType.PaperBook, Genre = Genre.Comic, }; var bookXml = ""; using (var stream = new StringWriter()) { var serializer = new XmlSerializer(typeof(Book)); serializer.Serialize(stream, book); bookXml = stream.ToString(); } using (var stream = new StringReader(bookXml)) { var serializer = new XmlSerializer(typeof(Book)); var deserialized = (Book)serializer.Deserialize(stream); Util.OnDemand("AttributePattern", () => Util.HorizontalRun(true, bookXml, deserialized)).Dump(); }
実行結果。
XmlRoot / XmlType
どちらもクラスに適用する属性だが、XmlRoot
はルート要素を表すクラス1つのみに適用できる。(子要素に複数適用できない)
クラス名と同じXML要素を出力する場合は、属性が無くても問題ない。
[XmlRoot] public class Book { } public class Publisher { } [XmlType("html")] public class Html { }
これらは、XML スキーマ定義ツールを使った場合に以下の違いが発生する。
<xs:element name="NewGroupName" type="NewTypeName" />
XmlSerializer
に渡すルートのクラスとXML要素名が違う場合は、XmlSerializer
のコンストラクタでXmlRoot
を指定できる。
var publisherDiffXml = ""; using (var stream = new StringWriter()) { var serializer = new XmlSerializer(typeof(Publisher), new XmlRootAttribute("PublisherRoot")); serializer.Serialize(stream, publisher); publisherDiffXml = stream.ToString(); } using (var stream = new StringReader(publisherDiffXml)) { var serializer = new XmlSerializer(typeof(Publisher), new XmlRootAttribute("PublisherRoot")); var deserialized = (Publisher)serializer.Deserialize(stream); Util.OnDemand("AttributePattern XmlRoot Diff", () => Util.HorizontalRun(true, publisherDiffXml, deserialized)).Dump(); }
XmlElement
メンバに適用する属性。メンバは子要素として扱われる。 メンバ名とXML要素を出力する場合は、属性が無くても問題ない。 パブリックなプロパティとフィールドどちらも対象で、フィールが先にXMLに出力される。
[XmlElement] public int Id { get; set; } public string Title { get; set; } public int Price; [XmlElement("Published")] public DateTime published;
XmlAttribute
メンバに適用する属性。XML属性として扱われる。
XML属性にする場合はXmlAttribute
必須、XML属性名を変える場合は引数で指定。
[XmlAttribute("ISBN10")] public string ISBN { get; set; } [XmlAttribute] public string ISBN13 { get; set; }
XmlIgnore
XMLに入出力されなくなる。
[XmlIgnore] public int Attribute { get; set; }
XmlEnum
enumのメンバに適用すると、XMLに出力する値を指定できる。
public enum BookType { PaperBook, EBook, } public enum Genre { [XmlEnum("コミック")] Comic, [XmlEnum("ラノベ")] LightNovel, }
XmlText
メンバに適用すると、メンバ名に対する要素が出力されず親要素の値として出力される。
public class Seller { [XmlText] public string Name { get; set; } }
XmlArray / XmlArrayItem
コレクションのメンバに適用する。
メンバ名とXML要素を出力する場合は、属性が無くても問題ない。
XmlArray
で親要素、XmlArrayItem
で子要素のXML要素名を指定できる。
Dictionary
はXmlSerializer
がサポートしていないので注意が必要。
public List<Author> Authors { get; set; } [XmlArray("Languages")] [XmlArrayItem("Language")] public List<string> Langs { get; set;} // Dictionaryは使えない //public Dictionary<string, string> NotSupported { get; set;}
子要素をベタに並べる
親要素なしでベタに並べる場合はXmlElement
を適用する。
[XmlElement("Seller")] public List<Seller> Sellers { get; set;}
不特定多数の子要素
子要素が不特定多数のXML要素で成り立っている場合、XmlElement
にType
の引数を指定する。
[XmlType("html")] public class Html { [XmlElement("div", typeof(Div))] [XmlElement("p", typeof(P))] public List<object> Items { get; set; } public class Div { [XmlText] public string Text { get; set; } } public class P { [XmlText] public string Value { get; set; } } }
var mixed = new Html { Items = new List<object> { new Html.Div { Text = "Hoge" }, new Html.P { Value = "Fuga" }, new Html.P { Value = "PiyoPiyo" }, } }; var mixedXml = ""; using (var stream = new StringWriter()) { var serializer = new XmlSerializer(typeof(Html)); serializer.Serialize(stream, mixed); mixedXml = stream.ToString(); } using (var stream = new StringReader(mixedXml)) { var serializer = new XmlSerializer(typeof(Html)); var deserialized = (Html)serializer.Deserialize(stream); Util.OnDemand("MixedList", () => Util.HorizontalRun(true, mixedXml, deserialized)).Dump(); }
フォーマット
nullの扱い
XML属性
XmlAttribute
単体では Nullable を扱えない。
string
の代替プロパティを用意して、Nullable のプロパティはXmlIgnore
にして対応する。
// XmlAttribute は Nullable 使えない [XmlAttribute] public int Attribute { get; set; } // XmlAttribute の Nullable は文字列の代替プロパティで回避 [XmlIgnore] public int? Attribute2 { get; set; } [XmlAttribute("Attribute2")] public string Attribute2String { get { return this.Attribute2?.ToString().ToLower(); } set { this.Attribute2 = value != null ? int.Parse(value) : null; } }
XML要素
NullableのXmlElement
でnullだった場合、<Element xsi:nil="true" />
が出力される。
nullの場合にXML要素自体出力しないようにするには、bool型の「プロパティ名+Specified」プロパティを定義すると、false
の時は出力されなくなる。空タグの制御もこれで可能。
Browsable(false)
を適用するとインテリセンスに出なくなる。
// XmlElement は null の場合、「<Element xsi:nil="true" />」が出力される [XmlElement] public int? Element { get; set; } // null の場合にタグを出力しないようにするには、「プロパティ名+Specified」プロパティで制御する public int? Element2 { get; set;} [XmlIgnore, Browsable(false)] public bool Element2Specified => Element2.HasValue;
出力フォーマット
XmlElement
のDataType
引数でちょっとしたフォーマットの指定くらいはできるが、細かくフォーマットを指定したい場合は、string
の代替プロパティを用意する。
[XmlElement] public DateTime FixedDateTimeRaw { get; set; } [XmlElement(DataType = "date")] public DateTime FixedDate { get; set; } [XmlElement(DataType = "dateTime")] public DateTime FixedDateTime { get; set; } [XmlElement(DataType = "time")] public DateTime FixedTime { get; set; } // DateTimeなど出力のフォーマットを調整する場合も文字列の代替プロパティを使う [XmlIgnore] public DateTime SomeDate { get; set; } [XmlElement("SomeDate")] public string SomeDateString { get { return this.SomeDate.ToString("yyyy/MM/dd HH:mm:ss.fff"); } set { this.SomeDate = DateTime.Parse(value); } }
オプション
XML宣言にUTF-8を指定する
XmlSerializer
はデフォルトだと、XML宣言にutf-16
を設定する。
utf-8
を設定する場合は直接指定できないため、StringWriter
を継承したクラスを自作する必要がある。
public class Utf8StringWriter : StringWriter { public override Encoding Encoding => Encoding.UTF8; } private void WriteStringWithUTF8(PurchaseOrder order) { using (var stream = new Utf8StringWriter()) { var serializer = new XmlSerializer(typeof(PurchaseOrder)); serializer.Serialize(stream, order); Util.OnDemand("WriteStringWithUTF8", () => stream.ToString()).Dump(); } }
XML宣言を出力しない
var settings = new XmlWriterSettings { // XML宣言除去 OmitXmlDeclaration = true, }; using (var stream = new StringWriter()) using (var writer = XmlWriter.Create(stream, settings)) { var serializer = new XmlSerializer(typeof(PurchaseOrder)); serializer.Serialize(writer, order); Util.OnDemand("WriteStringWithOption", () => stream.ToString()).Dump(); }
名前空間の宣言を出力しない
// 名前空間の宣言除去 var namespaces = new XmlSerializerNamespaces(); namespaces.Add("", ""); using (var stream = new StringWriter()) using (var writer = XmlWriter.Create(stream)) { var serializer = new XmlSerializer(typeof(PurchaseOrder)); serializer.Serialize(writer, order, namespaces); Util.OnDemand("WriteStringWithOption", () => stream.ToString()).Dump(); }
インデントを付ける
var settings = new XmlWriterSettings { // XML宣言除去 OmitXmlDeclaration = true, // インデント付き Indent = true, // 1つのインデントに使う文字列 IndentChars = " ", }; // 名前空間の宣言除去 var namespaces = new XmlSerializerNamespaces(); namespaces.Add("", ""); using (var stream = new StringWriter()) using (var writer = XmlWriter.Create(stream, settings)) { var serializer = new XmlSerializer(typeof(PurchaseOrder)); serializer.Serialize(writer, order, namespaces); Util.OnDemand("WriteStringWithOption", () => stream.ToString()).Dump(); }