4

特に複雑なオブジェクトを表すクラスのセットを作成しようとしていますが、それらのクラスの1つに、3つの可能な派生クラスの基本(抽象)クラスとして設定されたプロパティがあります。シリアル化と逆シリアル化を処理するようにASP.NETWebAPIを設定しています。つまり、デフォルトでは、JSONにJson.NETを使用します。POSTまたはPUTを介して送信されたJSONを適切な派生クラスに適切に逆シリアル化するようにWebAPIを取得するにはどうすればよいですか?

抽象メンバーを持つクラスは次のようになります(明確にするため、およびXmlSerializerを使用してxmlを逆シリアル化するために完全に機能するため、Xmlデコレーターを含めています)

[Serializable]
public class FormulaStructure {
    [XmlElement("column", typeof(ColumnStructure))]
    [XmlElement("function", typeof(FunctionStructure))]
    [XmlElement("operand", typeof(OperandStructure))]
    public AFormulaItemStructure FormulaItem;
}

抽象クラスはかなり基本的です:

[Serializable]
public abstract class AFormulaItemStructure { }

そして、抽象クラスには3つの派生物があります。

[Serializable]
public class ColumnStructure: AFormulaItemStructure {
    [XmlAttribute("type")]
    public string Type;

    [XmlAttribute("field")]
    public string Field;

    [XmlAttribute("display")]
    public string Display;
}

[Serializable]
public class FunctionStructure: AFormulaItemStructure {
    [XmlAttribute("type")]
    public string Type;

    [XmlAttribute("name")]
    public string Name;

    [XmlElement("parameters")]
    public string Parameters;
}


[Serializable]
public class OperandStructure: AFormulaItemStructure {
    [XmlAttribute("type")]
    public string Type;

    [XmlElement("left")]
    public string Left;

    [XmlElement("right")]
    public string Right;
}

現在、[DataContract]属性を使用すると、Json.NETフォーマッターは派生クラスへの入力に失敗し、プロパティを残しますnull


質問

同じクラスのXmlSerializer 属性と属性 を混在させることはできますか?設計したxmlでxml属性を 使用しているため使用していますが、xmlスキーマを自分で開発しているため、必要に応じて変更できます。DataContractSerializer XmlSerializer

Json.NETで同等のものは何 [KnownType()] ですか?DataContractSerializerJson.NETはのバージョンを尊重していないようですKnownType。適切なタイプを決定するために、自分のJsonConverterをロールする必要がありますか?

XmlとJsonの両方でオブジェクトを適切に逆シリアル化するように、DataContractSerializer または適切に逆シリアル化する ように、クラスをどのように装飾しますか? 私の目標は、これをASP.NET Web APIに組み込むことです。そのため、要求されたタイプに応じて、XmlまたはJsonを柔軟に生成できるようにします。Json.NETが機能しない場合、この複雑なクラスを操作するために使用する必要のある代替フォーマッターはありますか?DataContractJsonSerializer

必ずしも.NETクラス名をオブジェクトに含めずに、クライアント側でオブジェクトを生成する機能が必要です。


テストと改良

Web APIのテストでは、デフォルトのシリアル化がクライアントに送信されます。

{"FormulaItem":{"type":"int","field":"my_field","display":"My Field"}}

これは私の目的にとって理想的です。ただし、これをAPIに戻し、適切な派生型に逆シリアル化することは機能しません(プロパティに対してnullを生成します)。

以下のTommyGrovnesの回答をテストすると、彼DataContractSerializerはテストに使用して次のように生成します。

{"FormulaItem":{"__type":"column:#ExpressionStructureExperimentation.Models","display":"My Field","field":"my_field","type":"int"}}

これは私にとってもコードの保守性にとっても機能しません(これらのオブジェクトを生成するために名前空間全体をJavaScriptにハードコーディングすると、リファクタリングはPITAになります)。

4

3 に答える 3

3

すでに述べたように混合できますが、必要はないと思います。自分でWEB apiを使用したことはありませんが、WCF RestはDataContractsからxmlとjsonを生成し(Xml ..タグなし)、次のようにクラスにタグを付けます。

[DataContract]
public class FormulaStructure
{
    [DataMember]
    public AFormulaItemStructure FormulaItem;
}

[DataContract]
[KnownType(typeof(ColumnStructure))]
[KnownType(typeof(FunctionStructure))]
[KnownType(typeof(OperandStructure))]
public abstract class AFormulaItemStructure { }

[DataContract(Name="column")]
public class ColumnStructure : AFormulaItemStructure
{
    [DataMember(Name="type")]
    public string Type;

    [DataMember(Name = "field")]
    public string Field;

    [DataMember(Name = "display")]
    public string Display;
}

[DataContract(Name="function")]
public class FunctionStructure : AFormulaItemStructure
{
    [DataMember(Name = "type")]
    public string Type;

    [DataMember(Name = "name")]
    public string Name;

    [DataMember(Name = "parameters")]
    public string Parameters;
}

[DataContract(Name = "operand")]
public class OperandStructure : AFormulaItemStructure
{
    [DataMember(Name = "type")]
    public string Type;

    [DataMember(Name = "left")]
    public string Left;

    [DataMember(Name = "right")]
    public string Right;
}

生成されたXML/JSONをさらに制御する必要がある場合は、これをさらに微調整する必要があります。私はこのコードを使用してテストしました:

    public static string Serialize(FormulaStructure structure)
    {
        using (MemoryStream memoryStream = new MemoryStream())
        using (StreamReader reader = new StreamReader(memoryStream))
        {
            var serializer = new DataContractSerializer(typeof(FormulaStructure));
            serializer.WriteObject(memoryStream, structure);
            memoryStream.Position = 0;
            return reader.ReadToEnd();
        }
    }

    public static FormulaStructure Deserialize(string xml)
    {
        using (Stream stream = new MemoryStream())
        {
            byte[] data = System.Text.Encoding.UTF8.GetBytes(xml);
            stream.Write(data, 0, data.Length);
            stream.Position = 0;
            var deserializer = new DataContractSerializer(typeof(FormulaStructure));
            return (FormulaStructure)deserializer.ReadObject(stream);
        }
    }
于 2012-08-29T18:47:21.310 に答える
2

以前の回答でさらに問題が発生した後、SerializationBinderJSONが名前空間のシリアル化/逆シリアル化に使用できるクラスを発見しました。

コードファースト

SerializationBinder:を継承するクラスを生成しました

public class KnownTypesBinder : System.Runtime.Serialization.SerializationBinder {
    public KnownTypesBinder() {
        KnownTypes = new List<Type>();
        AliasedTypes = new Dictionary<string, Type>();
    }
    public IList<Type> KnownTypes { get; set; }
    public IDictionary<string, Type> AliasedTypes { get; set; }
    public override Type BindToType(string assemblyName, string typeName) {
        if (AliasedTypes.ContainsKey(typeName)) { return AliasedTypes[typeName]; }
        var type = KnownTypes.SingleOrDefault(t => t.Name == typeName);
        if (type == null) {
            type = Type.GetType(Assembly.CreateQualifiedName(assemblyName, typeName));
            if (type == null) {
                throw new InvalidCastException("Unknown type encountered while deserializing JSON.  This can happen if class names have changed but the database or the JavaScript references the old class name.");
            }
        }

        return type;
    }

    public override void BindToName(Type serializedType, out string assemblyName, out string typeName) {
        assemblyName = null;
        typeName = serializedType.Name;
    }
}

使い方

このように定義されたクラスのセットがあるとしましょう:

public class Class1 {
    public string Text { get; set; }
}

public class Class2 {
    public int Value { get; set; }
}

public class MyClass {
    public Class1 Text { get; set; }
    public Class2 Value { get; set; }
}

エイリアスタイプ

これにより、シリアル化/逆シリアル化されるクラスの独自の名前を生成できます。私のglobal.asaxファイルでは、バインダーを次のように適用します。

KnownTypesBinder binder = new KnownTypesBinder()
binder.AliasedTypes["Class1"] = typeof(Project1.Class1);
binder.AliasedTypes["WhateverStringIWant"] = typeof(Project1.Class2);

var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.Binder = binder;

これで、たとえばJSONとしてシリアル化するたびにMyClass、次のようになります。

{ 
    item: { 
        $type: "Project1.MyClass",  
        Text: {
            $type: "Class1",
            Text: "some value"
        },
        Value: {
            $type: "WhateverStringIWant",
            Value: 88
        }
    } 
}

既知のタイプ

KnownTypesBinderまた、アセンブリ情報を削除し、 :に情報を追加することでクラス名を厳密に使用することもできます。

KnownTypesBinder binder = new KnownTypesBinder()
binder.KnownTypes.Add(typeof(Project1.Class1));
binder.KnownTypes.Add(typeof(Project1.Class1));

与えられた2つの例では、Class1は同じ方法で参照されています。ただし、たとえば、にリファクタリングClass1すると、NewClass1この2番目の例は別の名前の送信を開始します。タイプを使用しているかどうかによって、それは大したことではないかもしれません。

最終的な考え

AliasedTypesの利点は、必要な文字列を指定できることです。コードをどれだけリファクタリングしても、.NETとJavaScript(またはそこにあるコンシューマー)間の通信は途切れません。

AliasedTypeまったく同じクラス名を持つsとsを混在させないように注意してください。これは、が勝つようKnownTypeにコードが記述されているためです。バインダーがタイプ(エイリアスまたは既知)を認識しない場合、バインダーはタイプの完全なアセンブリ名を提供します。AliasTypeKnownType

于 2013-07-25T14:04:36.527 に答える
0

最後に、リファクタリングを容易にするために、.NETクラス情報を分解して文字列変数のモジュールに追加しました。

module.net = {};
module.net.classes = {};
module.net.classes['column'] = "ColumnStructure";
module.net.classes['function'] = "FunctionStructure";
module.net.classes['operand'] = "OperandStructure";
module.net.getAssembly = function (className) {
    return "MyNamespace.Models." + module.net.classes[className] + ", MyAssembly";
}

JSONを次のように生成しました

{
    "FormulaItem": {
        "$type": module.net.getAssembly('column'),
        "type": "int",
        "field": "my_field",
        "display": "My Field"
    }
}
于 2012-09-16T19:37:31.993 に答える