C# と .NET Core のお話です。
仕事で XML で書かれたファイルを扱うことがありました。XDocument(いわゆる LINQ to XML というやつ)で動的に扱うのもいいんですが、今回はカスタムモデルにデシリアライズして扱いたかったので、そのとき調べたことをメモとして残しておきます。
主な手順とサンプルコードは以下の通りです。今回は使いまわしやすいように静的クラスにしました。
(Newtonsoft.Json みたいに一発でやってくれるライブラリがあれば便利なのに・・・)
デシリアライズ
- XML 形式の文字列を XDocument に置換
- XmlSerializer で XDocument をカスタムモデルにデシリアライズ
シリアライズ
- XmlSerializer でカスタムモデルを XDocument にシリアライズ
- XDocument を XML 形式の文字列に置換
Program.cs
using System.Collections.Generic;
using System.IO;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Serialization;
namespace ConsoleApp1
{
public class Program
{
public static void Main(string[] args)
{
// ファイルを読み込んで XDocument に置換 → XDocument をカスタムモデルにデシリアライズ
XDocument xml = XDocument.Load(Path.Combine(Directory.GetCurrentDirectory(), @"XMLFile1.xml"));
var myModel = XmlConvert.DeserializeObject<MyModel>(xml);
// カスタムモデルを XDocument にシリアライズ → XDocument を XML 形式でファイルに書き込み
var doc = XmlConvert.SerializeXDocument(myModel);
doc.Save("@"XMLFile2.xml");
// カスタムモデルを文字列にシリアライズ
var str = XmlConvert.SerializeObject(myModel);
}
}
public static class XmlConvert
{
public static string SerializeObject(object value, XmlSerializerNamespaces namespaces = null, XmlWriterSettings settings = null)
{
return SerializeXDocument(value, namespaces, settings).ToString();
}
public static XDocument SerializeXDocument(object value, XmlSerializerNamespaces namespaces = null, XmlWriterSettings settings = null)
{
if (namespaces == null)
{
namespaces = new XmlSerializerNamespaces();
namespaces.Add(string.Empty, string.Empty);
}
var doc = new XDocument();
using (var writer = XmlWriter.Create(doc.CreateWriter(), settings))
{
var xs = new XmlSerializer(value.GetType());
xs.Serialize(writer, value, namespaces);
}
return doc;
}
public static T DeserializeObject<T>(string xml, XmlReaderSettings settings = null)
where T : class
{
return DeserializeObject<T>(XDocument.Parse(xml), settings);
}
public static T DeserializeObject<T>(XDocument document, XmlReaderSettings settings = null)
where T : class
{
return DeserializeObject<T>(document.Root, settings);
}
public static T DeserializeObject<T>(XElement element, XmlReaderSettings settings = null)
where T : class
{
using (var reader = XmlReader.Create(element.CreateReader(), settings))
{
var xs = new XmlSerializer(typeof(T));
return (T)xs.Deserialize(reader);
}
}
}
[XmlRoot("Root")]
public class MyModel
{
[XmlElement("FirstElement")]
public string FirstElement { get; set; }
[XmlArray("SecondElements")]
[XmlArrayItem("SecondElement")]
public List<string> SecondElements { get; set; }
}
}
XMLFile1.xml
<?xml version="1.0" encoding="utf-8"?>
<Root>
<FirstElement>1つ目だよ</FirstElement>
<SecondElements>
<SecondElement>2つ目の1つ目だよ</SecondElement>
<SecondElement>2つ目の2つ目だよ</SecondElement>
<SecondElement>2つ目の3つ目だよ</SecondElement>
</SecondElements>
</Root>
課題
今回、XmlSerializer.Deserialize() を使用するとき、object 型や dynamic 型にデシリアライズしようとすると、以下の例外が発生してしまいました。カスタムモデルに XmlRootAttribute がないときも同様の例外が発生しました。object 型や dynamic 型にアノテーションがないため、名前空間ごとデシリアライズしようとして発生しているものと推測していますが、解決策がわからず今回は諦めました。。。
System.InvalidOperationException: 'There is an error in XML document (0, 0).'
内部例外
InvalidOperationException: <Root xmlns=''> was not expected.