初めに
とある理由でList<T>派生の独自のコレクションクラスを作成した時に、JSON/XMLへのシリアライズに問題が発生したので、調査した結果をまとめたものを記載。
問題とは、以下の3点だった。
- コレクションクラスのメンバーが出力されない
- 出力時に例外が発生する
- JSONにXML文字列が出力される
コレクションクラスの構成
今回使ったクラスは、以下のようなもの。
List<T>のようなコレクションクラスに、データ保持の最大容量を示すMaxをメンバーとして持つクラス
一つ目はList<T>派生型。
class Hoge<T> : List<T>
{
public int Max {get; set;}
}
二つ目はIEnumerable<T>/ ICollection<T>を有する独自コレクションクラス。
class Hoge<T> : IEnumerable<T>, ICollection<T>
{
public List<T> Array {get; set; }
public int Max {get; set; }
}
こちらの方は、Arrayのメソッドをそのまま流用しインターフェースを実装するような形にしている。
シリアライズの上位側実装
以下のようなメソッドを作成し、WriteFile(ファイル名,バッファ)でファイル出力するようにした。
public static void WriteFile<T>(string filename, T target)
{
switch (Path.GetExtension(filename)) {
case ".json":
var json = new DataContractJsonSerializer(typeof(T));
using (var stream = File.Create(filename)) {
using (var writer = JsonReaderWriterFactory.CreateJsonWriter(stream, Encoding.UTF8, true, true)) {
json.WriteObject(writer, target);
}
}
break;
case ".xml":
var xml = new XmlSerializer(typeof(T));
using (var stream = new StreamWriter(filename, false, Encoding.UTF8)) {
xml.Serialize(stream, target);
}
break;
}
}
出力方法
次の3つの方法でJSON/XMLの出力がどのように変わるかを検証。
- クラスにDataContract属性を指定、出力メンバーにDataMember属性を指定
- クラスにCollectionDataContract属性を指定、出力メンバーにDataMember属性を指定
- クラスにIXmlSerializableを実装
出力結果
DataContractを使った場合
形式 | JSON出力結果 | XML出力結果 |
List<T>派生 | 例外が発生 | <ArrayOfInt> <int>1</int> <int>2</int> </ArrayOfInt> |
独自 | { “Array”: [ 1, 2 ], “Max”: 3 } |
<RingList> <int>1</int> <int>2</int> </RingList> |
結果的には、望む出力ができたのは、独自クラスをJSON形式で出力した場合のみ。
それ以外は例外が出る、XML側ではメンバーが出力されない結果となった。
CollectionDataContractを使った場合
形式 | JSON出力結果 | XML出力結果 |
List<T>派生 | [ 1, 2 ] |
<ArrayOfInt> <int>1</int> <int>2</int> </ArrayOfInt> |
独自 | [ 1, 2 ] |
<RingList> <int>1</int> <int>2</int> </RingList> |
こちらは全滅。
すべてメンバーが出力されていない。
IXmlSerializableを実装した場合
形式 | XML出力結果 | JSON出力結果 |
List<T>派生 | <RingListOfInt64 Max=”20″> <long>1</long> <long>2</long> </FixedSizeList1OfInt64> |
“<RingListOfInt64 Max=\”20\”> <long xmlns=\”\”> 1 <\/long> <long xmlns=\”\”> 2 <\/long> <\/RingListOfInt64>” |
独自 | <RingListOfInt64 Max=”3″> <long>5</long> <long>6</long> </FixedSizeListOfInt64> |
“<RingListOfInt64 Max=\”20\”> <long xmlns=\”\”> 1 <\/long> <long xmlns=\”\”> 2 <\/long> <\/RingListOfInt64>” |
XMLは想定通りの出力となったが、JSONはXML文字列がそのまま出力された。
補足
複雑なクラス構成で出力を試したところ、IXmlSerializableを実装しJSON形式で出力した場合、IXmlSerializable を実装したクラスのみがXML化した文字列をJSON形式に埋め込まれていた。
{
"Hage": 10,
"buf1": "<FixedSizeList1Oflong Max=\"20\" xmlns=\"http:\/\/schemas.datacontract.org\/2004\/07\/Collections.Generic\"><long xmlns=\"\">1<\/long><long xmlns=\"\">2<\/long><\/FixedSizeList1Oflong>",
"buf2": {
"_array": [
3,
4,
0,
0
],
"_head": 0,
"_size": 2,
"_tail": 2,
"_version": 3
},
"buf3": "<FixedSizeListOflong Max=\"3\" xmlns=\"http:\/\/schemas.datacontract.org\/2004\/07\/Collections.Generic\"><long xmlns=\"\">5<\/long><long xmlns=\"\">6<\/long><\/FixedSizeListOflong>",
"buf4": [
7,
8
]
}
結果
List<T>派生型の場合、クラスの属性としてCollectionDataContractを使わないといけなくなる。
またCollectionDataContractとした場合には、出力されるのはコレクション内のデータのみで、クラスに設定されているメンバーはどんな形でも出力はされない結果となった。
そのため、クラス内のメンバーを出力させたい場合は、以下の様にする必要があるようだ。
- JSONでの出力は、独自クラスにしDataContract属性で設計する
- XMLでの出力は、IXmlSerializableで実装する
DataContractとIXmlSerializableは一緒に実装できず排他関係にあるらしく、同一コードでJSON/XMLを出力しようとする場合は、IXmlSerializableを実装しXML<->JSON変換クラスを別途用意する必要があるようだ。
こちらの変換については、今回試すところまでは行っていない。
この問題が分かった当初は、google検索で答えを求めようとしたのだが、質問自体数件で、答えとしては、クラスはIEnumerable<T>/ ICollection<T>を付けない単独クラスの中にコンテナメンバーを詰めろ、といった回答で自分が求めるものではなかった。
質問内容も、「Listに制約を付けたコレクションクラスを作りたい」ではなく、「フットボールチームを表すクラスをList<T>の派生で作成したい」だったからかもしれない。
コメント