
सीरियलाइज़ेशन और डिसिएरलाइज़ेशन विशिष्ट संचालन हैं जो आधुनिक डेवलपर तुच्छ मानते हैं। हम डेटाबेस के साथ संवाद करते हैं, HTTP अनुरोध उत्पन्न करते हैं, REST API के माध्यम से डेटा प्राप्त करते हैं, और अक्सर यह भी नहीं सोचते कि यह कैसे काम करता है। आज मैं सुझाव देता हूं कि JSON के लिए मेरे धारावाहिक निर्माता और deserializer लिखने का पता लगाएं कि हुड के नीचे क्या है।
त्याग
पिछली बार की तरह, मैं नोटिस करूंगा: हम एक आदिम धारावाहिक लिखेंगे, कोई कह सकता है कि एक साइकिल। यदि आपको टर्नकी समाधान की आवश्यकता है, तो
Json.NET का उपयोग
करें । इन लोगों ने एक अद्भुत उत्पाद जारी किया जो उच्च अनुकूलन योग्य है, बहुत कुछ कर सकता है और
पहले से
ही उन समस्याओं को हल कर रहा है जो JSON के साथ काम करते समय उत्पन्न होती हैं। अपने स्वयं के समाधान का उपयोग करना वास्तव में अच्छा है, लेकिन केवल अगर आपको अधिकतम प्रदर्शन, विशेष अनुकूलन की आवश्यकता होती है, या आप बाइक को पसंद करते हैं, तो मैं उन्हें पसंद करता हूं।
विषय क्षेत्र
JSON से ऑब्जेक्ट प्रतिनिधित्व में परिवर्तित करने की सेवा में कम से कम दो सबसिस्टम होते हैं। Deserializer एक सबसिस्टम है जो मान्य
JSON (टेक्स्ट) को हमारे प्रोग्राम के अंदर ऑब्जेक्ट प्रतिनिधित्व में बदल देता है। देशीकरण में टोकन शामिल है, अर्थात् JSON को तार्किक तत्वों में पार्स करना। Serializer एक सबसिस्टम है जो व्युत्क्रम कार्य करता है: JSON में डेटा के ऑब्जेक्ट प्रतिनिधित्व को बदलता है।
उपभोक्ता अक्सर निम्नलिखित इंटरफ़ेस देखता है। मैंने जानबूझकर इसे मुख्य तरीकों को उजागर करने के लिए सरलीकृत किया जो सबसे अधिक बार उपयोग किए जाते हैं।
public interface IJsonConverter { T Deserialize<T>(string json); string Serialize(object source); }
"हुड के तहत," deserialization में टोकेनाइजेशन (एक JSON टेक्स्ट को पार्स करना) और कुछ प्राइमिटिव्स का निर्माण करना शामिल है जो बाद में ऑब्जेक्ट प्रतिनिधित्व बनाने में आसान बनाते हैं। प्रशिक्षण के उद्देश्यों के लिए, हम मध्यवर्ती प्राइमेटिव्स के निर्माण को छोड़ देंगे (उदाहरण के लिए, जोबजेक्ट, Json.NET से JProperty) और हम तुरंत ऑब्जेक्ट पर डेटा लिखेंगे। यह एक ऋण है, क्योंकि यह अनुकूलन के लिए विकल्पों को कम करता है, लेकिन एक लेख के ढांचे के भीतर पूरी लाइब्रेरी बनाना असंभव है।
tokenization
आपको याद दिला दूं कि टोकेनाइजेशन या
लेक्सिकल एनालिसिस की प्रक्रिया इसमें निहित डेटा का एक अलग, अधिक कठोर प्रतिनिधित्व प्राप्त करने के उद्देश्य से पाठ की पार्सिंग है। आमतौर पर, इस प्रतिनिधित्व को
टोकन या टोकन कहा जाता है। JSON को पार्स करने के प्रयोजनों के लिए, हमें गुणों, उनके मूल्यों, संरचनाओं की शुरुआत और अंत के प्रतीकों को उजागर करना चाहिए - अर्थात्, टोकन जिन्हें कोड में JsonToken के रूप में दर्शाया जा सकता है।
JsonToken एक संरचना है जिसमें एक मान (पाठ) होता है, साथ ही एक प्रकार का टोकन भी होता है। JSON एक सख्त संकेतन है, इसलिए सभी प्रकार के टोकन
अगले एनम तक कम किए जा सकते हैं। बेशक, आने वाले डेटा (पंक्ति और स्तंभ) में अपने निर्देशांक के टोकन को जोड़ना अच्छा होगा, लेकिन डिबगिंग कार्यान्वयन के दायरे से परे है, जिसका अर्थ है कि JsonToken में यह डेटा शामिल नहीं है।
इसलिए, पाठ को टोकन में पार्स करने का सबसे आसान तरीका है कि प्रत्येक चरित्र को क्रमिक रूप से पढ़ा जाए और पैटर्न के साथ तुलना की जाए। हमें यह समझने की जरूरत है कि इस या उस प्रतीक का क्या मतलब है। यह संभव है कि कीवर्ड (सच्चा, झूठा, अशक्त) इस चरित्र से शुरू होता है, यह संभव है कि यह रेखा (उद्धरण चिह्न) की शुरुआत है, या शायद यह चरित्र स्वयं एक टोकन है ([,], {,}})। सामान्य विचार इस तरह दिखता है:
var tokens = new List<JsonToken>(); for (int i = 0; i < json.Length; i++) { char ch = json[i]; switch (ch) { case '[': tokens.Add(new JsonToken(JsonTokenType.ArrayStart)); break; case ']': tokens.Add(new JsonToken(JsonTokenType.ArrayEnd)); break; case '"': string stringValue = ReadString(); tokens.Add(new JsonToken(JsonTokenType.String, stringValue); break; ... } }
कोड को देखते हुए, ऐसा लगता है कि आप पढ़े गए डेटा के साथ तुरंत पढ़ सकते हैं और कुछ कर सकते हैं। उन्हें संग्रहीत करने की आवश्यकता नहीं है, उन्हें तुरंत उपभोक्ता को भेजा जाना चाहिए। इस प्रकार, एक निश्चित IEnumerator भीख माँगता है, जो पाठ को टुकड़ों में पार्स करेगा। सबसे पहले, यह आवंटन को कम करेगा, क्योंकि हमें मध्यवर्ती परिणामों (टोकन की एक सरणी) को संग्रहीत करने की आवश्यकता नहीं है। दूसरे, हम काम की गति बढ़ाएंगे - हां, हमारे उदाहरण में इनपुट एक स्ट्रिंग है, लेकिन एक वास्तविक स्थिति में, इसे
स्ट्रीम (एक फ़ाइल या नेटवर्क से) से
बदल दिया जाएगा, जिसे हम क्रमिक रूप से पढ़ते हैं।
मैंने
JsonTokenizer कोड तैयार किया है, जो
यहां पाया जा सकता
है । विचार समान है - टोकनधारक क्रमिक रूप से रेखा के साथ जाता है, यह निर्धारित करने की कोशिश करता है कि प्रतीक या उनके अनुक्रम को क्या संदर्भित करता है। यदि यह समझ में आता है, तो हम उपभोक्ता को एक टोकन और हस्तांतरण नियंत्रण बनाते हैं। यदि यह अभी तक स्पष्ट नहीं है, तो पढ़ें।
वस्तुओं का वर्णन करने की तैयारी करना
सबसे अधिक बार, JSON से डेटा परिवर्तित करने का अनुरोध Deserialize जेनेरिक विधि के लिए एक कॉल है, जहां
TOut वह डेटा प्रकार है जिसके साथ JSON टोकन मैप किए जाने चाहिए। जहाँ
प्रकार है : यह
प्रतिबिंब और
अभिव्यक्ति पेड़ लगाने का समय है। एक्सप्रेशनट्रीस के साथ काम करने की मूल बातें, साथ ही संकलित अभिव्यक्तियाँ "नंगे" प्रतिबिंब से बेहतर क्यों हैं, मैंने
अपने ऑटोमैपर बनाने के तरीके के बारे में पिछले लेख में वर्णन किया था। यदि आप Expression.Labmda.Compile () के बारे में कुछ नहीं जानते हैं - तो मैं इसे पढ़ने की सलाह देता हूं। यह मुझे लगता है कि मैपर का उदाहरण काफी समझ में आया।
तो, एक वस्तु डिसेरिएलाइज़र बनाने की योजना इस ज्ञान पर आधारित है कि हम किसी भी समय टुट प्रकार से संपत्ति प्रकार प्राप्त कर सकते हैं, अर्थात संपत्तिइन्फो संग्रह। इसी समय, संपत्ति प्रकार JSON संकेतन द्वारा सीमित हैं: संख्या, तार, सरणियाँ और ऑब्जेक्ट। भले ही हम अशक्त के बारे में नहीं भूलते हैं, यह इतना नहीं है क्योंकि यह पहली नज़र में लग सकता है। और अगर प्रत्येक आदिम प्रकार के लिए हम एक अलग deserializer बनाने के लिए मजबूर हो जाएंगे, तो सरणियों और वस्तुओं के लिए हम सामान्य कक्षाएं बना सकते हैं। यदि आप थोड़ा सोचते हैं, तो सभी सीरियल-डिजायर-डेसिलेलाइज़र (या
कन्वर्टर्स ) को निम्न इंटरफ़ेस में कम किया जा सकता है:
public interface IJsonConverter<T> { T Deserialize(JsonTokenizer tokenizer); void Serialize(T value, StringBuilder builder); }
आदिम प्रकार के एक दृढ़ता से टाइप किए गए कनवर्टर का कोड जितना संभव हो उतना सरल है: हम टोकन से वर्तमान JsonToken को निकालते हैं और इसे पार्स करके एक मूल्य में बदल देते हैं। उदाहरण के लिए, फ़्लोट.पर्स (currentToken.Value)।
BoolConverter या
FloatConverter पर नज़र
डालें - कुछ भी जटिल नहीं है। अगला, अगर आपको बूल के लिए एक deserializer की आवश्यकता है? या फ्लोट ?, इसे भी जोड़ा जा सकता है।
ऐरे डीरिएलाइज़ेशन
JSON से एक सरणी परिवर्तित करने
के लिए
सामान्य वर्ग कोड भी अपेक्षाकृत सरल है। यह हम टाइप कर सकते हैं तत्व के प्रकार के द्वारा
मानकीकृत है ।etElementType () । यह निर्धारित करना कि एक प्रकार एक सरणी भी सरल है:
Type.IsArray । ऐरे डीसेरिएलाइज़ेशन tokenizer.MoveNext () कहने के लिए नीचे आता है जब तक कि ArrayEnd टाइप टोकन नहीं पहुंच जाता। सरणी तत्वों का deserialization सरणी तत्व प्रकार का deserialization है, इसलिए, ArrayConverter बनाते समय, तत्व deserializer इसे पास किया जाता है।
कभी-कभी सामान्य कार्यान्वयन की तात्कालिकता के साथ कठिनाइयाँ होती हैं, इसलिए मैं आपको तुरंत बताऊंगा कि यह कैसे करना है। परावर्तन आपको वास्तविक समय में सामान्य प्रकार बनाने की अनुमति देता है, जिसका अर्थ है कि हम बनाए गए प्रकार को Activator.CreateInstance के तर्क के रूप में उपयोग कर सकते हैं। इसका लाभ लें:
Type elementType = arrayType.GetElementType(); Type converterType = typeof(ArrayConverter<>).MakeGenericType(elementType); var converterInstance = Activator.CreateInstance(converterType, object[] args);
वस्तुओं के एक deserializer बनाने के लिए तैयारियों को समाप्त करना, आप
JConverter के मुखौटे में deserializers के निर्माण और भंडारण से जुड़े सभी बुनियादी ढांचे के कोड डाल सकते हैं। वह सभी JSON सीरीज़ेशन और डिसेरिएलाइज़ेशन संचालन के लिए जिम्मेदार होगा और उपभोक्ताओं के लिए एक सेवा के रूप में उपलब्ध है।
ऑब्जेक्ट डिसरियलाइजेशन
मुझे आपको याद दिलाना है कि आप इस तरह से टाइप टी के सभी गुण प्राप्त कर सकते हैं: टाइपोफ (टी)। गेटप्रोपरेटी ()। प्रत्येक प्रॉपर्टी के लिए, आप
PropertyInfo.PropertyType को निकाल सकते हैं, जो हमें एक विशेष प्रकार के डेटा को क्रमिक और डीसर्लाइज़ करने के लिए एक टाइप किए गए IJsonConverter बनाने का अवसर देगा। यदि संपत्ति का प्रकार एक सरणी है, तो हम ArrayConverter को तुरंत चालू करते हैं या मौजूदा लोगों के बीच एक उपयुक्त पाते हैं। यदि संपत्ति प्रकार एक आदिम प्रकार है, तो जेसीऑन्टर कंस्ट्रक्टर में उनके लिए deserializers (कन्वर्टर्स) पहले से ही बनाए गए हैं।
परिणामी कोड को जेनेरिक क्लास
ObjectConverter में देखा जा सकता है। इसके निर्माणकर्ता में एक सक्रियक बनाया जाता है, गुणों को विशेष रूप से तैयार किए गए शब्दकोश से निकाला जाता है, और उनमें से प्रत्येक के लिए एक डीरियलाइज़ेशन विधि बनाई जाती है - एक्शन <टोबजेक्ट, जोंसटॉकेनाइज़र>। यह आवश्यक है, सबसे पहले, वांछित संपत्ति के साथ IJsonConverter को तुरंत संबद्ध करने के लिए, और दूसरी बात, आदिम प्रकारों को निकालने और लिखने पर बॉक्सिंग से बचने के लिए। प्रत्येक डिसेरिएलाइज़ेशन विधि यह जानती है कि आउटगोइंग ऑब्जेक्ट की कौन सी प्रॉपर्टी रिकॉर्ड की जाएगी, वैल्यू के डीरिज़िलाइज़र को सख्ती से टाइप किया गया है और वैल्यू को उसी रूप में लौटाता है जिस रूप में इसकी ज़रूरत होती है।
एक संपत्ति के लिए एक IJsonConverter का बंधन इस प्रकार है:
Type converterType = propertyValueConverter.GetType(); ConstantExpression Expression.Constant(propertyValueConverter, converterType); MethodInfo deserializeMethod = converterType.GetMethod("Deserialize"); var value = Expression.Call(converter, deserializeMethod, tokenizer);
एक्सप्रेशन.संस्थान स्थिर सीधे अभिव्यक्ति में बनाया गया है, जो संपत्ति के मूल्य के लिए डिसेरिएलाइज़र उदाहरण के लिए एक संदर्भ संग्रहीत करता है। यह वास्तव में स्थिर नहीं है कि हम "नियमित सी #" में लिखते हैं, क्योंकि यह एक संदर्भ प्रकार को संग्रहीत कर सकता है। अगला, Deserialize विधि deserializer प्रकार से पुनर्प्राप्त की जाती है, जो वांछित प्रकार का मान लौटाता है, और फिर इसे कहा जाता है -
Expression.Call । इस प्रकार, हमें एक ऐसा तरीका मिलता है जो जानता है कि कहां और क्या लिखना है। यह डिक्शनरी में रखने के लिए रहता है और इसे तब कॉल करता है जब प्रॉपर्टी टाइप का टोकन वांछित नाम "आता है" टोकन से आता है। एक और प्लस यह है कि यह सब बहुत जल्दी काम करता है।
कितनी तेजी से
साइकिलें, जैसा कि बहुत शुरुआत में उल्लेख किया गया था, यह कई मामलों में लिखने के लिए समझ में आता है: यदि यह समझने का प्रयास है कि प्रौद्योगिकी कैसे काम करती है, या आपको कुछ विशेष परिणाम प्राप्त करने की आवश्यकता है। उदाहरण के लिए, गति। आप यह सुनिश्चित कर सकते हैं कि
तैयार किए गए परीक्षणों के साथ deserializer वास्तव में deserializes (मैं परीक्षण डेटा प्राप्त करने के लिए
AutoFixture का उपयोग करता
हूं )। वैसे, आपने शायद गौर किया कि मैंने वस्तुओं का क्रमांकन भी लिखा था। लेकिन चूंकि लेख काफी बड़ा हो गया है, मैं इसका वर्णन नहीं करूंगा, लेकिन सिर्फ बेंचमार्क देंगे। हां, पिछले लेख के साथ की तरह, मैंने
बेंचमार्कडॉटनेट लाइब्रेरी का उपयोग करके बेंचमार्क लिखा।
बेशक, मैंने JSON के साथ काम करने के लिए सबसे आम और अनुशंसित समाधान के रूप में न्यूटनसॉफ्ट (Json.NET) के साथ डिसेरिएलाइज़ेशन गति
की तुलना की । इसके अलावा, उनकी वेबसाइट पर यह सही लिखा गया है: DataContractJsonSerializer की तुलना में 50% अधिक तेज़, और JavaScriptSerializer की तुलना में 250% अधिक तेज़। संक्षेप में, मैं जानना चाहता था कि मेरा कोड कितना खो जाएगा। परिणामों ने मुझे आश्चर्यचकित किया: ध्यान दें कि डेटा आवंटन लगभग तीन गुना कम है, और डीरियलाइज़ेशन दर लगभग दो गुना तेज है।
डेटा क्रमांकन के दौरान गति और आवंटन की तुलना ने और भी अधिक रोचक परिणाम प्राप्त किए। यह पता चला है कि बाइक के धारावाहिक को लगभग पांच गुना कम आवंटित किया गया है और लगभग तीन गुना तेजी से काम किया है। अगर गति ने वास्तव में मुझे (वास्तव में) बहुत परेशान किया, तो यह एक स्पष्ट सफलता होगी।
हां, गति मापने के दौरान, मैंने Json.NET वेबसाइट पर पोस्ट की गई
उत्पादकता बढ़ाने के लिए युक्तियों का उपयोग नहीं किया। मैंने बॉक्स से माप लिया, जो कि सबसे अधिक इस्तेमाल किए जाने वाले परिदृश्य के अनुसार है: JsonConvert.DeserializeObject। प्रदर्शन को बेहतर बनाने के अन्य तरीके हो सकते हैं, लेकिन मैं उनके बारे में नहीं जानता।
निष्कर्ष
क्रमबद्धता और डीरिएलाइज़ेशन की अपेक्षाकृत उच्च गति के बावजूद, मैं अपने स्वयं के समाधान के पक्ष में Json.NET को छोड़ने की सलाह नहीं दूंगा। गति में लाभ की गणना मिलीसेकंड में की जाती है, और वे आसानी से नेटवर्क देरी, डिस्क या कोड में "डूब जाते हैं", जो कि क्रमबद्धता लागू होने के स्थान के ऊपर स्थित है। ऐसे मालिकाना समाधान का समर्थन करने के लिए नरक है, जहां केवल डेवलपर्स जो विषय में पारंगत हैं, उन्हें अनुमति दी जा सकती है।
ऐसी साइकिलों का दायरा ऐसे अनुप्रयोग हैं जो पूरी तरह से उच्च प्रदर्शन, या पालतू परियोजनाओं के लिए डिज़ाइन किए गए हैं, जहां आप समझते हैं कि यह या यह तकनीक कैसे काम करती है। मुझे उम्मीद है कि मैंने इस सब में आपकी थोड़ी मदद की है।