2012-09-07 7 views
34

प्रोजेक्ट एक Asp.Net वेब API वेब सेवा है।कस्टम जेसन कनवर्टर (वेब ​​एपीआई) से Json.Net JsonSerializer में स्वयं संदर्भ लूप

मेरे पास एक प्रकार का पदानुक्रम है जिसे मुझे जेसन से और से क्रमबद्ध करने में सक्षम होना चाहिए, इसलिए मैंने इस SO: How to implement custom JsonConverter in JSON.NET to deserialize a List of base class objects? से कोड लिया है, और कनवर्टर को मेरे पदानुक्रम के बेस क्लास पर लागू किया है; कुछ इस तरह (वहाँ छद्म कोड irrelevancies को छिपाने के लिए यहाँ है):

[JsonConverter(typeof(TheConverter))] 
public class BaseType{ 
    ///note the base of this type here is from the linked SO above 
    private class TheConverter : JsonCreationConverter<BaseType>{ 
    protected override BaseType Create(Type objectType, JObject jObject){ 
     Type actualType = GetTypeFromjObject(jObject); /*method elided*/ 
     return (BaseType)Activator.CreateInstance(actualType); 
    } 
    } 
} 

public class RootType { 
    public BaseType BaseTypeMember { get; set; } 
} 

public class DerivedType : BaseType {  
} 

तो अगर मैं एक RootType उदाहरण जिसका BaseTypeMemberDerivedType का एक उदाहरण के बराबर था, तो यह उस प्रकार का एक उदाहरण में वापस deserialized किया जाएगा deserialize ।

रिकॉर्ड के लिए, इन JSON ऑब्जेक्ट्स में '$type' फ़ील्ड होता है जिसमें वर्चुअल टाइप नाम होते हैं (पूर्ण नेट नाम नहीं हैं) ताकि मैं जेएसओएन में प्रकारों का समर्थन कर सकूं जबकि वास्तव में नियंत्रित किया जा सकता है कि किस प्रकार को क्रमबद्ध और deserialized किया जा सकता है।

अब यह अनुरोध से मूल्यों को deserializing के लिए वास्तव में अच्छी तरह से काम करता है; लेकिन मुझे serialization के साथ एक मुद्दा है। यदि आप लिंक किए गए एसओ, और वास्तव में जेसन.Net चर्चा को शीर्ष उत्तर से लिंक करते हैं, तो आप देखेंगे कि मैं जिस बेस कोड का उपयोग कर रहा हूं वह पूरी तरह से deserialization के आसपास तैयार है; इसके उपयोग के उदाहरणों के साथ serializer के मैन्युअल निर्माण दिखा रहा है। इस JsonCreationConverter<T> द्वारा तालिका में लाए गए JsonConverter कार्यान्वयन को आसानी से NotImplementedException फेंकता है।

अब, वेब एपीआई अनुरोध के लिए एक एकल फॉर्मेटर का उपयोग करने के तरीके के कारण, मुझे WriteObject विधि में 'मानक' क्रमिकरण को लागू करने की आवश्यकता है।

मैं इस बिंदु पर तनाव होना चाहिए कि मैं सब कुछ था अपने प्रोजेक्ट के इस हिस्से पर आरंभ करने से पहले त्रुटियों बिना ठीक से serializing।

तो मैं इस किया था:

public override void WriteJson(JsonWriter writer, 
    object value, 
    JsonSerializer serializer) 
{ 
    serializer.Serialize(writer, value); 
} 

लेकिन मैं एक JsonSerializationException मिलती है: Self referencing loop detected with type 'DerivedType', जब वस्तुओं में से एक धारावाहिक है। दोबारा - अगर मैं कनवर्टर विशेषता को हटाता हूं (मेरी कस्टम सृजन को अक्षम करता है) तो यह ठीक काम करता है ...

मुझे एहसास है कि इसका मतलब है कि मेरा सीरियलाइजेशन कोड वास्तव में कनवर्टर को उसी ऑब्जेक्ट पर ट्रिगर कर रहा है, जो बदले में serializer फिर से कॉल - विज्ञापन मतली। की पुष्टि की - मेरा उत्तर देख

तो क्या कोड चाहिए मैं WriteObject में लेखन किया है कि एक ही 'मानक' क्रमबद्धता कि काम करता है क्या करेंगे?

+1

अच्छा क्यू एंड ए, लेकिन यह उनका कहना है कि serializer कर रही है लायक है सही बात: यह कस्टम धारावाहिकों की तलाश में ऑब्जेक्ट का निरीक्षण करता है और एक पाता है। यदि आप कुछ और करते हैं तो आपको चिंतित होना चाहिए अन्यथा यह विदेशी वर्गों पर कस्टम कन्वर्टर्स का सम्मान नहीं कर सकता है। –

+0

@RupertRawnsley yep मैं उस –

उत्तर

49

खैर यह मजेदार था ...

जब मैं अपवाद के लिए स्टैक ट्रेस में और अधिक बारीकी से देखा, मैंने देखा है कि विधि JsonSerializerInternalWriter.SerializeConvertable में वहाँ दो बार था, वास्तव में यह था कि विधि एक के ऊपर से स्टैक - JsonSerializerInternalWriter.CheckForCircularReference का आह्वान - जो बदले में अपवाद फेंक रहा था। हालांकि, यह मेरे स्वयं के कनवर्टर Write विधि के लिए कॉल का स्रोत भी था।

तो यह प्रतीत होता है कि serializer कर रहा था:

  • 1) वस्तु एक कनवर्टर
    • 1 क) फेंक दिया है, तो वृत्तीय संदर्भ
    • 1b) आह्वान कनवर्टर के लिखें विधि
  • 2) अन्य
    • 2 ए) आंतरिक धारावाहिकों का उपयोग करें

तो, इस मामले में, Json.Net मेरी कनवर्टर जो बारी में Json.Net serializer जो तब तक चल रही है, क्योंकि यह देखता है यह पहले से ही उद्देश्य यह है कि यह करने के लिए पारित किया गया था serializing है बुला रहा है बुला रहा है ! और JsonSerializerInternalWriter.SerializeValue को SerializeConvertable से कॉल स्टैक ऊपर जा रहा है, कोड का पता लगाता है कि क्या एक कनवर्टर हो सकता है इस्तेमाल किया जाना चाहिए - DLL (! लेकिन मैं 'कॉल' कार्यक्षमता चाहते हैं हाँ मैं जानता हूँ कि यह खुला स्रोत है) पर

उद्घाटन ILSpy शुरू के पास पाया:

if (((jsonConverter = ((member != null) ? member.Converter : null)) != null 
    || (jsonConverter = ((containerProperty != null) ? containerProperty.ItemConverter 
                : null)) != null 
    || (jsonConverter = ((containerContract != null) ? containerContract.ItemConverter 
                : null)) != null 
    || (jsonConverter = valueContract.Converter) != null 
    || (jsonConverter = 
     this.Serializer.GetMatchingConverter(valueContract.UnderlyingType)) != null 
    || (jsonConverter = valueContract.InternalConverter) != null) 
    && jsonConverter.CanWrite) 
{ 
    this.SerializeConvertable(writer, jsonConverter, value, valueContract, 
           containerContract, containerProperty); 
    return; 
} 

शुक्र है कि if बयान में आखिरी शर्त अपनी समस्या से समाधान प्रदान करता है: सभी मैं करना था या तो आधार कनवर्टर में कोड से नकल करने के लिए निम्न जोड़ने के लिए था प्रश्न में एसओ से जुड़ा हुआ है, या व्युत्पन्न में:

public override bool CanWrite 
{ 
    get 
    { 
     return false; 
    } 
} 

और अब यह सब ठीक काम करता है।

इस का नतीजा यह है, तथापि, कि अगर आप एक वस्तु पर कुछ कस्टम JSON क्रमबद्धता के लिए चाहते हैं और आप एक कनवर्टर के साथ यह इंजेक्शन लगाने रहे और आप के तहत मानक क्रमबद्धता तंत्र पर वापस आने का इरादा है कुछ या सभी स्थितियों; तो आप ऐसा नहीं कर सकते क्योंकि आप सोचने में ढांचे को मूर्ख बना देंगे कि आप एक परिपत्र संदर्भ स्टोर करने की कोशिश कर रहे हैं।

मैं ReferenceLoopHandling सदस्य से छेड़छाड़ की कोशिश किया था, लेकिन अगर मैं इसे Ignore के लिए कहा था उन्हें तो कुछ भी नहीं धारावाहिक था और अगर मैं उन्हें बचाने के लिए यह कहा था, आश्चर्य, मैं एक ढेर अतिप्रवाह मिला है।

यह संभव है कि यह जेसननेट में एक बग है - ठीक है, यह बहुत बढ़िया मामला है कि यह ब्रह्मांड के किनारे से गिरने का खतरा है - लेकिन यदि आप इस स्थिति में खुद को पाते हैं तो आप ' अटक गया!

+2

से सहमत हूं मुझे कहना है कि यह एक बढ़िया मामला नहीं है और कस्टम जेसन कन्वर्टर्स के साथ इस मुद्दे से संबंधित बहुत सारे धागे हैं। मैं पद की सराहना करता हूं। धन्यवाद! – GFXGunblade

+0

यह पूरी तरह से बेकार है। आपको मैन्युअल रूप से "$ प्रकार" को एम्बेड करने का प्रयास करना होगा, जो ढांचा निजी चर और कक्षाओं में आपको जो कुछ भी चाहिए, उसे छुपाता है, ताकि आप जेसन कनवर्टर के लिखितजेसन विधि के साथ धारावाहिक का उपयोग भी नहीं कर सकें। भयानक। – Triynko

+0

मैंने अपना खुद का ढांचा लिखा और यह सही काम करता है। यह आपको एक गतिशील वस्तु से पढ़ने या लिखने की अनुमति देता है, और यह कॉल के बाहर प्रकार के नाम और अन्य विशेष पैरामीटर एम्बेड करने को संभालता है। – Triynko

2

मैं सिर्फ यह अपने आप में आए और मैं निराशा में मेरे बाल बाहर खींच रहा था!

इस मुद्दे को हल करने के लिए, निम्नलिखित मेरे लिए काम किया, लेकिन क्योंकि मैं CanWrite समाधान याद किया, यह एक अधिक जटिल समाधान है।

  • मौजूदा कक्षा की एक प्रति बनाएं जिस पर आप अपने कनवर्टर का उपयोग कर रहे हैं और इसे कुछ अलग कहते हैं।
  • प्रतिलिपि पर JsonConverter विशेषता हटाएं।
  • नई कक्षा पर एक निर्माता बनाएं जो मूल वर्ग के समान प्रकार का पैरामीटर लेता है।बाद में serialization के लिए आवश्यक किसी भी मूल्य पर प्रतिलिपि बनाने के लिए कन्स्ट्रक्टर का उपयोग करें।
  • अपने कनवर्टर की WriteJson विधि में, मान को अपने डमी प्रकार में रूपांतरित करें, फिर इसके बजाय उस क्रमबद्ध क्रमबद्ध करें।

    public class FakeMyResponse 
    { 
        public ResponseBlog blog { get; set; } 
        public Post[] posts { get; set; } 
    
        public FakeMyResponse(MyResponse response) 
        { 
         blog = response.blog; 
         posts = response.posts; 
        } 
    } 
    

    WriteJson है:

    public override void WriteJson(JsonWriter writer, object value, 
        JsonSerializer serializer) 
    { 
        if (CanConvert(value.GetType())) 
        { 
         FakeMyResponse response = new FakeMyResponse((MyResponse)value); 
         serializer.Serialize(writer, response); 
        } 
    } 
    

    [JsonConverter(typeof(MyResponseConverter))] 
    public class MyResponse 
    { 
        public ResponseBlog blog { get; set; } 
        public Post[] posts { get; set; } 
    } 
    

    प्रति इस तरह दिखता है:

उदाहरण के लिए, यह मेरा मूल वर्ग के समान है

संपादित करें:

ओपी ने इंगित किया कि Expando का उपयोग करना एक और संभावित समाधान हो सकता है। यह अच्छी तरह से काम करता है, नई कक्षा बनाने की परेशानी को बचाता है, हालांकि डीएलआर समर्थन के लिए फ्रेमवर्क 4.0 या बाद में आवश्यकता होती है। दृष्टिकोण एक नया dynamicExpandoObject बनाने और उसके बाद WriteJson विधि में उसके गुण आरंभ करने के लिए सीधे प्रतिलिपि बनाने के लिए है, जैसे:

public override void WriteJson(JsonWriter writer, object value, 
    JsonSerializer serializer) 
{ 
    if (CanConvert(value.GetType())) 
    { 
     var response = (MyResponse)value; 
     dynamic fake = new System.Dynamic.ExpandoObject(); 
     fake.blog = response.blog; 
     fake.posts = response.posts; 
     serializer.Serialize(writer, fake); 
    } 
} 
+1

काम करता है, जब आपको वास्तव में कनवर्टर और मानक क्रमिकरण दोनों का उपयोग करने की आवश्यकता होती है, तो गंभीर समस्या का उपन्यास समाधान है - क्योंकि हमारे JSON serializable ऑब्जेक्ट्स * होते हैं * प्रकाश होने के कारण, प्रतिलिपि उन लोगों में बहुत खराब नहीं होती है स्थितियों। मुझे लगता है, एक एक्सपोन्डो भी काम कर सकता है। –

+0

एक्सपोन्डो समाधान बहुत ही सुरुचिपूर्ण है। मैं कुछ अन्य स्थानों को देख सकता हूं जहां यह मेरी परियोजना में उपयोगी होगा। धन्यवाद :) –

+0

ओह हाँ, सिवाय इसके कि आप पूरी तरह से प्रकार की जानकारी खो देते हैं। वह ऑब्जेक्ट में "$ प्रकार" फ़ील्ड नहीं लिखेंगे, इसलिए टाइप किए गए ऑब्जेक्ट के रूप में इसे deserializing भूल जाओ, खासकर यदि लक्ष्य सदस्य एक इंटरफ़ेस है और एम्बेडेड ठोस प्रकार की आवश्यकता है। साथ ही फ्रेमवर्क निजी वैरिएबल में "$ प्रकार" लिखने के लिए आवश्यक सभी चीज़ों को छुपाता है (ऑब्जेक्ट $ आईडी मान अकेले छोड़ दें), जिससे आप अपने मूल्यों को एम्बेड करना असंभव हो जाते हैं। JSON.NET ढांचे में गंभीर डिज़ाइन त्रुटियां हैं; लगभग अनुपयोगी। – Triynko

2

मैं सिर्फ माता-पिता/बच्चे संग्रह के साथ एक ही plroblem था और उस पोस्ट है जिसमें पाया गया मेरा मामला हल किया। मैं केवल, माता पिता संग्रह आइटम की सूची दिखाना चाहते थे और बच्चे डेटा के किसी भी जरूरत नहीं थी इसलिए मैं निम्नलिखित का उपयोग करें और यह ठीक काम किया:

JsonConvert.SerializeObject(ResultGroups, Formatting.None, 
         new JsonSerializerSettings() 
         { 
          ReferenceLoopHandling = ReferenceLoopHandling.Ignore 
         }); 

यह भी पर Json.NET codplex पृष्ठ पर referes : किसी

http://json.codeplex.com/discussions/272371

+0

इसकी सराहना करते हैं, लेकिन यह इस विशेष प्रश्न में दिए गए मामले में काम नहीं करता है। –

+0

क्षमा करें एंड्रस, मैंने सोचा कि जेसनसेरियलज़र सेटिंग्स मदद करेगा। –

+0

सरल आसान समाधान जो बहुत अच्छा काम करता है - धन्यवाद –

0

यह मदद कर सकता है, लेकिन मेरे मामले में मैं अपने वस्तु मान प्रकार के रूप में माना है के बराबर विधि ओवरराइड करने के लिए कोशिश कर रहा था। मेरे अनुसंधान में, मैंने पाया कि JSON.NET यह पसंद नहीं करता है:

JSON.NET Self Referencing Error

6

मैं इस समस्या Newtonsoft.Json के संस्करण 4.5.7.15008 का उपयोग कर का सामना करना पड़ा। मैंने कुछ अन्य लोगों के साथ यहां दिए गए सभी समाधानों की कोशिश की। मैंने नीचे दिए गए कोड का उपयोग करके इस मुद्दे को हल किया। मूल रूप से आप serialization करने के लिए बस एक और JsonSerializer का उपयोग कर सकते हैं। जेसनसिरियालाइज़र जो बनाया गया है, उसके पास कोई पंजीकृत कन्वर्टर्स नहीं है, इसलिए पुनः प्रवेश/अपवाद से बचा जाएगा। यदि अन्य सेटिंग्स या कॉन्ट्रैक्ट रिसेल्वर का उपयोग किया जाता है तो उन्हें मैन्युअल रूप से बनाए गए धारावाहिक पर सेट करने की आवश्यकता होगी: कुछ कन्स्ट्रक्टर तर्क कस्टमकॉन्टर क्लास में इसे समायोजित करने के लिए जोड़ा जा सकता है।

public class CustomConverter : JsonConverter 
    { 
     /// <summary> 
     /// Use a privately create serializer so we don't re-enter into CanConvert and cause a Newtonsoft exception 
     /// </summary> 
     private readonly JsonSerializer noRegisteredConvertersSerializer = new JsonSerializer(); 

     public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 
     { 
      bool meetsCondition = false; /* add condition here */ 
      if (!meetsCondition) 
       writer.WriteNull(); 
      else 
       noRegisteredConvertersSerializer.Serialize(writer, value); 
     } 

     public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 
     { 
      throw new NotImplementedException(); 
     } 

     public override bool CanConvert(Type objectType) 
     { 
      // example: register accepted conversion types here 
      return typeof(IDictionary<string, object>).IsAssignableFrom(objectType); 
     } 
    } 
+1

यह फिर से प्रवेश की समस्या का एक आसान समाधान है जो फॉलबैक की अनुमति देता है - जैसे :) –

0

मेरा एक साधारण गलती थी, और इस विषय के समाधान से कोई लेना देना नहीं था।

यह विषय Google में पहला पृष्ठ था, इसलिए यदि मैं दूसरों के जैसा ही करता हूं तो मैं यहां पोस्ट कर रहा हूं।

dynamic table = new ExpandoObject(); 
.. 
.. 
table.rows = table; <<<<<<<< I assigned same dynamic object to itself. 
1

आईएमओ, यह पुस्तकालय की एक गंभीर सीमा है। समाधान काफी सरल है, हालांकि मैं स्वीकार करूंगा कि यह मेरे पास जल्दी नहीं आया था।

.ReferenceLoopHandling = ReferenceLoopHandling.Serialize 

जो, के रूप में हर जगह से प्रलेखित, स्वयं को संदर्भित त्रुटि को खत्म करने और एक ढेर अतिप्रवाह से प्रतिस्थापित कर देगा: समाधान स्थापित करने के लिए है। मेरे मामले में, मुझे लेखन कार्यक्षमता की आवश्यकता थी, इसलिए गलत करने के लिए CanWrite सेटिंग एक विकल्प नहीं था। अंत में मैं सिर्फ CanConvert कॉल की रक्षा के लिए जब मुझे पता है कि serializer के लिए एक कॉल कारण जा रहा है एक ध्वज सेट (अनंत) प्रत्यावर्तन:

Public Class ReferencingObjectConverter : Inherits JsonConverter 

     Private _objects As New HashSet(Of String) 
     Private _ignoreNext As Boolean = False 

     Public Overrides Function CanConvert(objectType As Type) As Boolean 
      If Not _ignoreNext Then 
       Return GetType(IElement).IsAssignableFrom(objectType) AndAlso Not GetType(IdProperty).IsAssignableFrom(objectType) 
      Else 
       _ignoreNext = False 
       Return False 
      End If 
     End Function 

     Public Overrides Sub WriteJson(writer As JsonWriter, value As Object, serializer As JsonSerializer) 

      Try 
       If _objects.Contains(CType(value, IElement).Id.Value) Then 'insert a reference to existing serialized object 
        serializer.Serialize(writer, New Reference With {.Reference = CType(value, IElement).Id.Value}) 
       Else 'add to my list of processed objects 
        _objects.Add(CType(value, IElement).Id.Value) 
        'the serialize will trigger a call to CanConvert (which is how we got here it the first place) 
        'and will bring us right back here with the same 'value' parameter (and SO eventually), so flag 
        'the CanConvert function to skip the next call. 
        _ignoreNext = True 
        serializer.Serialize(writer, value) 
       End If 
      Catch ex As Exception 
       Trace.WriteLine(ex.ToString) 
      End Try 

     End Sub 

     Public Overrides Function ReadJson(reader As JsonReader, objectType As Type, existingValue As Object, serializer As JsonSerializer) As Object 
      Throw New NotImplementedException() 
     End Function 

     Private Class Reference 
      Public Property Reference As String 
     End Class 

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