2011-11-24 7 views
19

私は、およそ以下の絵があります。私は、逆シリアル化中にFoo型のコンストラクタに型バーのオブジェクトを渡すために何とかしたいデフォルト以外のコンストラクタに引数を渡すにはどうすればよいですか?

public class Foo 
{ 
    public Foo(Bar bar, String x, String y) 
    { 
     this.Bar = bar; 
     this.X = x; 
     this.Y = y; 
    } 

    [JsonIgnore] 
    public Bar Bar { get; private set; } 

    public String X { get; private set; } 
    public String Y { get; private set; } 
} 

public class Bar 
{ 
    public Bar(String z) 
    { 
     this.Z = z; 
    } 

    public String Z { get; private set; } 
} 

、すなわち:

var bar = new Bar("Hello world"); 
var x = JsonConvert.DeserializeObject<Foo>(fooJsonString, bar); 

答えて

15

ここでは、問題の解決に関する私の考えです。 Netのカスタム非直列化APIは透過的ではなく、つまりクラス階層に影響します。

実際にはプロジェクトに10-20クラスがある場合は問題ありませんが、何千ものクラスのプロジェクトがあれば、JsonでOOPデザインを遵守する必要があるということは特に喜ばしいことではありません.Net要件。

Json.Netは、作成後にポピュレート(初期化)されるPOCOオブジェクトに適しています。しかし、すべてのケースで真実ではなく、コンストラクタ内でオブジェクトを初期化することがあります。そして、その初期化を行うために、あなたは '正しい'引数を渡す必要があります。これらの「正しい」引数は、シリアライズされたテキストの中に入れることも、以前に作成して初期化することもできます。残念ながら、非直列化中のJson.Netは、デフォルト値を彼が理解していない引数に渡し、私の場合は常にArgumentNullExceptionを引き起こします。

ソリューション:

ここ

あるアプローチシリアル化されたまたは非直列化されたいずれかの引数のいずれかのセットを使用して直列化復元時に実際のカスタムオブジェクトの作成を可能にする、主な問題は、サブ最適なアプローチは、それがの2つの段階を必要とすることですオブジェクトごとの直列化復元は、カスタム直列化復元を必要とするが、それは動作し、デシリアライズは、あなたがそれを必要とする方法をオブジェクトことができますので、ここに行く:

public class FactoryConverter<T> : Newtonsoft.Json.JsonConverter 
{ 
    /// <summary> 
    /// Writes the JSON representation of the object. 
    /// </summary> 
    /// <param name="writer">The <see cref="JsonWriter"/> to write to.</param> 
    /// <param name="value">The value.</param> 
    /// <param name="serializer">The calling serializer.</param> 
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 
    { 
     throw new NotSupportedException("CustomCreationConverter should only be used while deserializing."); 
    } 

    /// <summary> 
    /// Reads the JSON representation of the object. 
    /// </summary> 
    /// <param name="reader">The <see cref="JsonReader"/> to read from.</param> 
    /// <param name="objectType">Type of the object.</param> 
    /// <param name="existingValue">The existing value of object being read.</param> 
    /// <param name="serializer">The calling serializer.</param> 
    /// <returns>The object value.</returns> 
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 
    { 
     if (reader.TokenType == JsonToken.Null) 
      return null; 

     T value = CreateAndPopulate(objectType, serializer.Deserialize<Dictionary<String, String>>(reader)); 

     if (value == null) 
      throw new JsonSerializationException("No object created."); 

     return value; 
    } 

    /// <summary> 
    /// Creates an object which will then be populated by the serializer. 
    /// </summary> 
    /// <param name="objectType">Type of the object.</param> 
    /// <returns></returns> 
    public abstract T CreateAndPopulate(Type objectType, Dictionary<String, String> jsonFields); 

    /// <summary> 
    /// Determines whether this instance can convert the specified object type. 
    /// </summary> 
    /// <param name="objectType">Type of the object.</param> 
    /// <returns> 
    ///  <c>true</c> if this instance can convert the specified object type; otherwise, <c>false</c>. 
    /// </returns> 
    public override bool CanConvert(Type objectType) 
    { 
     return typeof(T).IsAssignableFrom(objectType); 
    } 

    /// <summary> 
    /// Gets a value indicating whether this <see cref="JsonConverter"/> can write JSON. 
    /// </summary> 
    /// <value> 
    ///  <c>true</c> if this <see cref="JsonConverter"/> can write JSON; otherwise, <c>false</c>. 
    /// </value> 
    public override bool CanWrite 
    { 
     get 
     { 
      return false; 
     } 
    } 
} 

まず我々はCustomCreationConverterクラスに以下の方法を再構築します

次は私達が私達のはFooを作成するファクトリクラスを作成します。ここでは

public class FooFactory : FactoryConverter<Foo> 
{ 
    public FooFactory(Bar bar) 
    { 
     this.Bar = bar; 
    } 

    public Bar Bar { get; private set; } 

    public override Foo Create(Type objectType, Dictionary<string, string> arguments) 
    { 
     return new Foo(Bar, arguments["X"], arguments["Y"]); 
    } 
} 

は、サンプルコードです。この場合、fooので

var bar = new Bar("BarObject"); 

var fooSrc = new Foo 
(
    bar, 
    "A", "B" 
); 

var str = JsonConvert.SerializeObject(fooSrc); 

var foo = JsonConvert.DeserializeObject<Foo>(str, new FooFactory(bar)); 

Console.WriteLine(str); 

は、私たちが中にFooのコンストラクタに渡すために必要な引数が含まれていますデシリアライゼーション。

9

を私は専門家ではありませんよJson.NETでは、AFAIKは単純に不可能です。私があなただったら、のデシリアライズの後にこのを修正するオプションを調べます。

ほとんどのシリアル化APIでは、その程度までの構築を制御できません。 4つの最も一般的なアプローチは、(最初​​の最も一般的)である:

  • パラメータなしのコンストラクタ
  • が明らか1はコンストラクタ使用
  • 完全コンストラクタをスキップ呼び出す:シリアライズ
  • あるメンバーに1マッピング
  • ユーザー提供のファクトリメソッドを使用してください

これは非常にまれです。 の外にのコンストラクタを実行することで解決しなければならない場合があります。

一部のシリアライゼーションAPIは、コンテキスト情報をコールバックに渡すなど、さまざまな時点(通常はシリアル化とデシリアライズの前後)でオブジェクトに対してメソッドを実行できる「シリアライゼーション/逆シリアル化コールバック」を提供します。 IF Json.NETは、逆シリアル化コールバックをサポートしています。 This questionは、実際には[OnDeserialized]のコールバックパターンがサポートされている可能性があることを示しています。 contextは、JsonSerializerSettings.Contextプロパティからのもので、オプションでdeserializeメソッドに指定できます。

それ以外の場合は、逆シリアル化した後に手動で実行してください。

マイラフ擬似コード(完全未テスト):

問題:

JSON

// inside type: Foo 
[OnDeserialized] 
public void OnDeserialized(StreamingContext ctx) { 
    if(ctx != null) { 
     Bar bar = ctx.Context as Bar; 
     if(bar != null) this.Bar = bar; 
    } 
} 

var ctx = new StreamingContext(StreamingContextStates.Other, bar); 
var settings = new JsonSerializerSettings { Context = ctx }; 
var obj = JsonConvert.DeserializeObject<Foo>(fooJsonString, settings); 
+10

シリアライザの作者がこの低い優先度を考慮する理由は考えられません。私は、パブリックインターフェイスベースのシリアライザが持つべき基本的な機能の1つを低レベルでハックしないで、不変のオブジェクトを構築できると常に考えてきました。 – CodesInChaos

+1

@CodeInChaos protobuf-netはリストされた4つのオプションをすべてサポートしています*と*少なくとも1つの他の(代理変換)...ただsayin ' –

+0

@CodeInChaosなぜ:単純に、それは本当にかわいいです –

0

パラメータがシリアル化されていない値のみのコンストラクタを使用している場合は、最初にインスタンスを作成してから、デシリアライズする代わりにオブジェクトを作成します。

public static void PopulateObject(
    string value,      // JSON string 
    object target)      // already-created instance 

あなたが特定の直列化の設定を持っている場合は、またJsonSerializerSettingsパラメータを含んで過負荷があります:次のようにJsonConvertクラスが定義され、PopulateObject方法があります。

シングルバーのパラメータを持つFooのコンストラクタを追加し、あなたのような何かができる:

var bar = new Bar("Hello World"); 
var foo = new Foo(bar); 
JsonConvert.PopulateObject(fooJsonString, foo); 
あなたがへの書き込みを許可するようにNHibernateのに調整をマッピングするためのフィールドを使用するか、または作るためにあなたのクラスを調整する必要があります

プライベートセッター(カスタムIProxyValidatorクラスの使用)。

関連する問題