2011-11-24 205 views
19

我有大約如下圖:如何將參數傳遞給非默認構造函數?

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; } 
} 

我想以某種方式反序列化過程式酒吧的對象傳遞給Foo類型的構造,即:

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

回答

15

這裏是我的關於問題解決的想法:

問題:

JSON。 Net的自定義反序列化api不透明,即影響我的類層次結構。

實際上,如果您的項目中有10-20個類,那麼這並不是問題,但如果您擁有數千個類的大型項目,那麼您對於需要遵從OOP設計和Json的事實並不是特別滿意.Net要求。

Json.Net與POCO對象很好,它們在創建後被填充(初始化)。但在所有情況下都不是事實,有時你會在構造函數中初始化你的對象。爲了進行初始化,您需要傳遞「正確的」參數。這些'正確的'參數可以在序列化文本中,或者可以在一段時間之前被創建和初始化。不幸的是,在反序列化過程中,Json.Net將默認值傳遞給他不明白的參數,在我的情況下,它總是導致ArgumentNullException。

解決辦法:

這裏是方法,允許使用任何一組參數或者串行化或非序列化反序列化過程真實自定義對象的創建,主要的問題是,該方法最優化的,它需要的2個相每個對象的反序列化,需要定製反序列化,但它的工作原理,並允許反序列化對象,你需要的方式,所以這裏有雲:

首先,我們重新組裝CustomCreationConverter類的方式如下:

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; 
     } 
    } 
} 

接下來,我們創建工廠類,將創建富:

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"]); 
    } 
} 

下面是示例代碼:

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包含了我們需要在傳遞給一個Foo構造函數的參數反序列化。

9

我不是專家在Json.NET上,但AFAIK根本不可能。如果我是你,我會考慮在反序列化之後修復這個的選項。

很少有序列化API可以讓您控制構建到那個程度;四種最典型的方法是(最常見的第一):

  • 調用參數構造
  • 跳過構造完全
  • 使用具有明顯的1構造:1映射到成員被序列
  • 使用用戶提供的工廠方法

這聽起來像你想要的最後一個,這是非常罕見的。你可能不得不爲這個這個以外的構造函數而做決定。

一些序列化API提供了「序列化/反序列化回調」,它允許您在各個點(通常在序列化和反序列化之前和之後)上運行對象上的方法,包括將某些上下文信息傳遞到回調中。 IF Json.NET支持反序列化回調,可能是要看的東西。 This question暗示[OnDeserialized]回調模式確實可以被支持; context來自JsonSerializerSettings.Context屬性,您可以選擇提供給反序列化方法。

否則,只需在反序列化後手動運行即可。

我粗略的僞碼(完全未經測試):

// 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

不知道爲什麼串行器編寫者認爲這個優先級低。我一直認爲能夠構建不變的對象,而不需要低級別的基於公共接口的序列化器應該具有的基本功能之一。 – CodesInChaos

+1

@CodeInChaos protobuf-net支持列出的所有4個選項,*和*至少一個其他(代理變換)...只是說' –

+0

@CodeInChaos至於爲什麼:簡單地說,這真的很難 –

0

如果你有一個構造函數,它的唯一參數是你的非序列化值,首先創建你的實例,然後填充你的對象而不是反序列化。該JsonConvert類有一個PopulateObject方法,定義如下:

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

如果你有特別的序列化設置,有一個重載,還包括JsonSerializerSettings參數。

添加具有單杆參數一個Foo的構造函數,你可以這樣做:

var bar = new Bar("Hello World"); 
var foo = new Foo(bar); 
JsonConvert.PopulateObject(fooJsonString, foo); 

您可能需要調整您的班級使用的字段映射,或調整NHibernate的以允許寫入私人二傳手(使用自定義類IProxyValidator類)。

相關問題