私は次のコンソールアプリケーションを持っています:
_using System;
using System.IO;
using System.Xml.Serialization;
using Newtonsoft.Json;
namespace OutputApp
{
public class Foo
{
public object Value1 { get; set; }
public string Value2 { get; set; }
}
public class Bar
{
public int Arg1 { get; set; }
public double Arg2 { get; set; }
}
class Program
{
public static Foo CreateFooBar()
{
return new Foo
{
Value1 = new Bar
{
Arg1 = 123,
Arg2 = 99.9
},
Value2 = "Test"
};
}
public static string SerializeXml(object obj)
{
using (var stream = new MemoryStream())
{
using (var reader = new StreamReader(stream))
{
var serializer = new XmlSerializer(obj.GetType());
serializer.Serialize(stream, obj);
stream.Position = 0;
return reader.ReadToEnd();
}
}
}
static void Main(string[] args)
{
var fooBar = CreateFooBar();
// Using Newtonsoft.Json
var json = JsonConvert.SerializeObject(fooBar, Formatting.Indented);
var xnode = JsonConvert.DeserializeXNode(json, "RootElement");
var xml = xnode.ToString();
// Using XmlSerializer, throws InvalidOperationException
var badXml = SerializeXml(fooBar);
Console.ReadLine();
}
}
}
_
私には2つのクラスがあります。クラスFoo
およびクラスBar
。クラスFoo
にはタイプobject
のプロパティがあります。これは、さまざまなオブジェクトを保持できるコントラクトであり、プロパティを具象型またはジェネリックに設定できないため、要件です。
次に、CreateFooBar()
メソッドを使用してダミーのfooBar
オブジェクトを作成します。その後、最初にそれをJSONにシリアル化します。これは、Json.Netでうまく機能します。次に、Json.NetのXMLコンバーターメソッドを使用して、json文字列をXNode
オブジェクトに変換します。それもうまく機能します。
両方の出力は次のとおりです。
_{
"Value1": {
"Arg1": 123,
"Arg2": 99.9
},
"Value2": "Test"
}
<RootElement>
<Value1>
<Arg1>123</Arg1>
<Arg2>99.9</Arg2>
</Value1>
<Value2>Test</Value2>
</RootElement>
_
これは機能しますが、後でxmlにシリアル化するためだけにjsonにシリアル化する必要があるため、これは確かにあまり良いことではありません。直接xmlにシリアル化したいのですが。
XmlSerializer
を使用してこれを行うと、悪名高いInvalidOperationExceptoinが発生します。これは、クラスをXmlInclude
属性で装飾しなかったか、 その他の回避策 のいずれかを実行したためです。
InvalidOperationException
タイプOutputApp.Barは予期されていませんでした。 XmlIncludeまたはSoapInclude属性を使用して、静的に認識されていないタイプを指定します。
XmlSerializerの回避策はどれも私見の良い解決策ではなく、オブジェクトをXMLにシリアル化することなく完全に実行可能であるため、その必要性はないと思います。 安っぽい 属性。
誰かがこれを行うことができる.NETの優れたXmlシリアライザーを知っていますか、またはこの機能をJson.Netに追加する計画はありますか?
何か案は?
Update1
私は属性の使用に反対していませんが、それは理にかなっている必要があります。 XmlInclude
属性について私が気に入らないのは、循環依存に強制されることです。基本クラスを定義するアセンブリAと、派生クラスを実装するアセンブリBがあるとします。 XmlInclude属性が機能する方法は、アセンブリAの基本クラスをアセンブリBの子クラスの型名で装飾する必要があるということです。これは、循環依存関係があり、失敗することを意味します。
Update2
コンソールアプリケーションをリファクタリングしてXmlSerializer
で動作させるソリューションを探しているのではなく、そこにあるものをXMLシリアル化する方法を探していることを明確にします。
以下に、データ型としてobject
を使用するのは不適切な設計であるというコメントがありました。これが真実であるかどうかにかかわらず、これはまったく別の議論です。重要なのは、XMLにシリアル化できない理由がないということです。そのような解決策を見つけたいと思っています。
個人的には、「マーカー」インターフェースの作成は汚いデザインだと思います。インターフェースを悪用して、単一の.NETクラス(XmlSerializer)の機能を回避します。シリアル化ライブラリを別のものと交換すると、マーカーインターフェイス全体が冗長になります。クラスを1つのシリアライザーに結合したくありません。
私はエレガントな解決策を探しています(もしあれば)?
XmlInclude属性でモデルを汚染する必要はありません。既知のすべてのクラスを XmlSerializer's constructor
に明示的に示すことができます。
var serializer = new XmlSerializer(obj.GetType(), new[] { typeof(Bar) });
また、基本クラスとしてobject
を使用することは、crappyアプローチのように見えます。少なくともマーカーインターフェイスを定義します。
public interface IMarker
{
}
Bar
が実装するもの:
public class Bar : IMarker
{
public int Arg1 { get; set; }
public double Arg2 { get; set; }
}
次に、Foo
クラスのValue1
プロパティを、宇宙で最も普遍的なタイプのようにするのではなく、このマーカーに特化します(そうすることはできません)。
public class Foo
{
public IMarker Value1 { get; set; }
public string Value2 { get; set; }
}
Coz now it's pretty trivial
マーカーインターフェイスを実装し、XmlSerializerコンストラクターに渡すすべての参照アセンブリで実行時にすべてのロードされた型を取得するには:
var type = typeof(IMarker);
var types = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(s => s.GetTypes())
.Where(p != type)
.Where(p => type.IsAssignableFrom(p))
.ToArray();
var serializer = new XmlSerializer(obj.GetType(), types);
これで、マーカーインターフェイスを実装するすべてのタイプを適切にシリアル化する方法を知っている非常に有能なXmlSerializer
ができました。 JSON.NETとほぼ同じ機能を実現しました。そして、このXmlSerializaerのインスタンス化は、ロードされたすべてのタイプを認識している Composition Root project
に存在する必要があることを忘れないでください。
また、object
を使用することは、設計上の決定として不適切です。