2011-11-24 23 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; } 
} 

मैं किसी भी तरह चाहते अक्रमांकन दौरान प्रकार फू के एक निर्माता के प्रकार बार की एक वस्तु पारित करने के लिए, अर्थात्:

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

उत्तर

15

यहाँ समस्या समाधान के बारे में मेरे विचार कर रहे हैं। नेट का कस्टम deserialization एपीआई पारदर्शी नहीं है, यानी मेरी कक्षा पदानुक्रम को प्रभावित करता है।

असल में यह कोई समस्या नहीं है जब आपके प्रोजेक्ट में 10-20 कक्षाएं हों, हालांकि यदि आपके पास हजारों कक्षाओं के साथ बड़ी परियोजना है, तो आप इस तथ्य के बारे में विशेष रूप से खुश नहीं हैं कि आपको जेसन के साथ अपने ओओपी डिज़ाइन का पालन करने की आवश्यकता है नेट आवश्यकताओं।

जेसन.Net पीओसीओ ऑब्जेक्ट्स के साथ अच्छा है जो बनाए जाने के बाद आबादी (प्रारंभिक) हैं। लेकिन यह सभी मामलों में सच नहीं है, कभी-कभी आप अपने ऑब्जेक्ट्स को कन्स्ट्रक्टर के अंदर शुरू करते हैं। और उस प्रारंभिकरण को करने के लिए आपको 'सही' तर्क पारित करने की आवश्यकता है।ये 'सही' तर्क या तो धारावाहिक पाठ के अंदर हो सकते हैं या वे पहले से ही बनाया जा सकता है और कुछ समय पहले शुरू किया जा सकता है। दुर्भाग्य से जेसन.Net deserialization के दौरान डिफ़ॉल्ट मानों को उन तर्कों से गुजरता है जिन्हें वह समझ में नहीं आता है, और मेरे मामले में यह हमेशा ArgumentNullException का कारण बनता है।

समाधान:

यहाँ है दृष्टिकोण है कि तर्क या तो धारावाहिक या गैर धारावाहिक के किसी सेट का उपयोग कर अक्रमांकन दौरान वास्तविक कस्टम वस्तु निर्माण की अनुमति देता है, मुख्य समस्या यह है कि दृष्टिकोण उप इष्टतम, इसके बारे में 2 चरणों की आवश्यकता है वस्तु प्रति अक्रमांकन कस्टम अक्रमांकन की आवश्यकता है कि है, लेकिन यह काम करता है और deserializing जिस तरह से आप इसकी आवश्यकता की वस्तुओं की अनुमति देता है, इसलिए यहाँ जाता है:

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 वर्ग निम्नलिखित तरीके से पुनः

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 में एक तर्क है कि हम के दौरान एक फू निर्माता को पारित करने के लिए आवश्यक होता है 363,210

अगला हम कारखाने वर्ग है कि हमारे फू पैदा करेगा बनाने अक्रमांकन।

9

मैं एक विशेषज्ञ नहीं हूँ जेसन.नेट पर, लेकिन AFAIK कि बस संभव नहीं है। अगर मैं आप थे, तो deserialization के बाद मैं को ठीक करने के विकल्पों को देखूंगा।

बहुत कम धारावाहिक API आपको उस डिग्री के निर्माण को नियंत्रित करने की अनुमति देगा; चार सबसे विशिष्ट दृष्टिकोण (सबसे सामान्य प्रथम) हैं:

  • parameterless निर्माता आह्वान
  • निर्माता को छोड़ पूरी तरह से
  • एक निर्माता एक स्पष्ट 1 है कि का उपयोग करें: सदस्यों की जा रही धारावाहिक
  • को 1 मानचित्रण
  • उपयोगकर्ता द्वारा आपूर्ति की गई फ़ैक्टरी विधि

ऐसा लगता है जैसे आप अंतिम चाहते हैं, जो कि दुर्लभ है। आपको को कन्स्ट्रक्टर के बाहर करने के लिए बसने पड़ सकते हैं।

कुछ सीरियलाइजेशन एपीआई "क्रमबद्धता/deserialization कॉलबैक" प्रदान करते हैं, जो आपको कॉलबैक में कुछ संदर्भ जानकारी पास करने सहित विभिन्न बिंदुओं (आमतौर पर दोनों धारावाहिक और deserialize से पहले और बाद में) पर एक विधि चलाने की अनुमति देता है। अगर जेसन.NET deserialization कॉलबैक का समर्थन करता है, तो यह देखने की बात हो सकती है। This question सुझाव देता है कि [OnDeserialized] कॉलबैक पैटर्न वास्तव में समर्थित हो सकता है; contextJsonSerializerSettings की .Context संपत्ति से आता है जो आप वैकल्पिक रूप से deserialize विधि को आपूर्ति कर सकते हैं।

अन्यथा, इसे deserialization के बाद मैन्युअल रूप से चलाएं।

मेरे किसी न किसी छद्म कोड (पूरी तरह से अपरीक्षित):

समस्या:

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

कोई विचार नहीं क्यों serializer लेखकों को यह कम प्राथमिकता मानते हैं। मैंने हमेशा कम-स्तरीय हैक के बिना अपरिवर्तनीय ऑब्जेक्ट्स बनाने में सक्षम होने पर विचार किया है, वास्तव में एक बुनियादी इंटरफेस आधारित सीरिएलाइज़र के पास मूलभूत सुविधाओं में से एक है। – CodesInChaos

+1

@CodeInChaos क्यों के लिए के रूप में Protobuf शुद्ध * कम से कम एक अन्य (किराए की रूपांतरण) का समर्थन करता है सूचीबद्ध सभी 4 विकल्प, * और ... बस 'कह –

+0

@CodeInChaos: बस, जो वास्तव में बहुत कठिन है –

0

यदि आपके पास एक निर्माता है जिसका केवल पैरामीटर आपके गैर-क्रमबद्ध मान हैं, तो अपना उदाहरण पहले बनाएं और फिर deserializing के बजाय अपनी ऑब्जेक्ट को पॉप्युलेट करें।

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

आप विशेष क्रमबद्धता सेटिंग हैं तो वहाँ एक अधिभार कि यह भी एक JsonSerializerSettings पैरामीटर शामिल है: इस प्रकार JsonConvert वर्ग, एक PopulateObject विधि है परिभाषित किया।

एक फू निर्माता एक भी बार पैरामीटर है कि जोड़ें, आप की तरह कुछ कर सकता है:

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

आप NHibernate करने के लिए मानचित्रण के लिए फ़ील्ड का उपयोग, या बनाने के समायोजन के लिए लिख अनुमति देने के लिए करने के लिए अपने वर्ग समायोजित करना पड़ सकता निजी सेटर्स (कस्टम IProxyValidator कक्षा का उपयोग)।

संबंधित मुद्दे