web-dev-qa-db-ja.com

Json.Netでできることと同様のXMLシリアル化

私は次のコンソールアプリケーションを持っています:

_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つのシリアライザーに結合したくありません。

私はエレガントな解決策を探しています(もしあれば)?

9
dustinmoris

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を使用することは、設計上の決定として不適切です。

5
Darin Dimitrov