paint-brush
टाइपस्क्रिप्ट में टाइप-सेफ JSON क्रमांकन में महारत हासिल करनाद्वारा@nodge
23,893 रीडिंग
23,893 रीडिंग

टाइपस्क्रिप्ट में टाइप-सेफ JSON क्रमांकन में महारत हासिल करना

द्वारा Maksim Zemskov11m2024/02/26
Read on Terminal Reader

बहुत लंबा; पढ़ने के लिए

यह आलेख JSON प्रारूप का उपयोग करते समय टाइपस्क्रिप्ट में डेटा क्रमबद्धता की चुनौतियों का पता लगाता है। यह विशेष रूप से JSON.stringify और JSON.parse फ़ंक्शन की कमियों पर केंद्रित है। इन समस्याओं के समाधान के लिए, यह यह सत्यापित करने के लिए JSONCompatible प्रकार के उपयोग का सुझाव देता है कि क्या प्रकार T को JSON में सुरक्षित रूप से क्रमबद्ध किया जा सकता है। इसके अलावा, यह JSON से सुरक्षित डिसेरिएलाइज़ेशन के लिए सुपरस्ट्रक्चर लाइब्रेरी की अनुशंसा करता है। यह विधि प्रकार की सुरक्षा में सुधार करती है और विकास के दौरान त्रुटि का पता लगाने में सक्षम बनाती है।
featured image - टाइपस्क्रिप्ट में टाइप-सेफ JSON क्रमांकन में महारत हासिल करना
Maksim Zemskov HackerNoon profile picture
0-item
1-item
2-item


लगभग हर वेब एप्लिकेशन को डेटा क्रमांकन की आवश्यकता होती है। यह आवश्यकता निम्न स्थितियों में उत्पन्न होती है:


  • नेटवर्क पर डेटा स्थानांतरित करना (जैसे HTTP अनुरोध, वेबसॉकेट)
  • HTML में डेटा एम्बेड करना (उदाहरण के लिए, हाइड्रेशन के लिए)
  • डेटा को लगातार स्टोरेज में संग्रहीत करना (जैसे लोकलस्टोरेज)
  • प्रक्रियाओं के बीच डेटा साझा करना (जैसे वेब वर्कर या पोस्टमैसेज)


कई मामलों में, डेटा हानि या भ्रष्टाचार के गंभीर परिणाम हो सकते हैं, जिससे एक सुविधाजनक और सुरक्षित क्रमांकन तंत्र प्रदान करना आवश्यक हो जाता है जो विकास चरण के दौरान यथासंभव त्रुटियों का पता लगाने में मदद करता है। इन उद्देश्यों के लिए, डेटा ट्रांसफर प्रारूप के रूप में JSON और विकास के दौरान स्थिर कोड जाँच के लिए टाइपस्क्रिप्ट का उपयोग करना सुविधाजनक है।


टाइपस्क्रिप्ट जावास्क्रिप्ट के सुपरसेट के रूप में कार्य करता है, जो JSON.stringify और JSON.parse जैसे कार्यों के निर्बाध उपयोग को सक्षम करना चाहिए, है ना? पता चला, अपने सभी लाभों के बावजूद, टाइपस्क्रिप्ट स्वाभाविक रूप से यह नहीं समझता है कि JSON क्या है और कौन से डेटा प्रकार JSON में क्रमांकन और डिसेरिएलाइज़ेशन के लिए सुरक्षित हैं।


आइए इसे एक उदाहरण से स्पष्ट करें।


टाइपस्क्रिप्ट में JSON के साथ समस्या

उदाहरण के लिए, एक फ़ंक्शन पर विचार करें जो कुछ डेटा को लोकलस्टोरेज में सहेजता है। चूंकि लोकलस्टोरेज वस्तुओं को संग्रहीत नहीं कर सकता है, हम यहां JSON क्रमबद्धता का उपयोग करते हैं:


 interface PostComment { authorId: string; text: string; updatedAt: Date; } function saveComment(comment: PostComment) { const serializedComment = JSON.stringify(comment); localStorage.setItem('draft', serializedComment); }


हमें लोकलस्टोरेज से डेटा पुनर्प्राप्त करने के लिए एक फ़ंक्शन की भी आवश्यकता होगी।


 function restoreComment(): PostComment | undefined { const text = localStorage.getItem('draft'); return text ? JSON.parse(text) : undefined; }


इस कोड में क्या खराबी है? पहली समस्या यह है कि टिप्पणी को पुनर्स्थापित करते समय, हमें updatedAt फ़ील्ड के लिए Date के बजाय एक string प्रकार मिलेगा।


ऐसा इसलिए होता है क्योंकि JSON में केवल चार आदिम डेटा प्रकार ( null , string , number , boolean ) होते हैं, साथ ही सरणियाँ और ऑब्जेक्ट भी होते हैं। JSON में Date ऑब्जेक्ट, साथ ही जावास्क्रिप्ट में पाए जाने वाले अन्य ऑब्जेक्ट्स: फ़ंक्शंस, मैप, सेट इत्यादि को सहेजना संभव नहीं है।


जब JSON.stringify एक ऐसे मान का सामना करता है जिसे JSON प्रारूप में प्रदर्शित नहीं किया जा सकता है, तो टाइप कास्टिंग होती है। Date ऑब्जेक्ट के मामले में, हमें एक स्ट्रिंग मिलती है क्योंकि Date ऑब्जेक्ट toJson() विधि लागू करता है, जो Date ऑब्जेक्ट के बजाय एक स्ट्रिंग लौटाता है।


 const date = new Date('August 19, 1975 23:15:30 UTC'); const jsonDate = date.toJSON(); console.log(jsonDate); // Expected output: "1975-08-19T23:15:30.000Z" const isEqual = date.toJSON() === JSON.stringify(date); console.log(isEqual); // Expected output: true


दूसरी समस्या यह है कि saveComment फ़ंक्शन PostComment प्रकार लौटाता है, जिसमें दिनांक फ़ील्ड Date प्रकार की होती है। लेकिन हम पहले से ही जानते हैं कि Date के बजाय, हमें एक string प्रकार प्राप्त होगा। टाइपस्क्रिप्ट हमें इस त्रुटि को ढूंढने में मदद कर सकता है, लेकिन ऐसा क्यों नहीं होता?


पता चला, टाइपस्क्रिप्ट की मानक लाइब्रेरी में, JSON.parse फ़ंक्शन को (text: string) => any के रूप में टाइप किया जाता है। any के उपयोग के कारण, टाइप चेकिंग अनिवार्य रूप से अक्षम है। हमारे उदाहरण में, टाइपस्क्रिप्ट ने बस हमारा वचन लिया कि फ़ंक्शन एक Date ऑब्जेक्ट वाला PostComment लौटाएगा।


यह टाइपस्क्रिप्ट व्यवहार असुविधाजनक और असुरक्षित है। यदि हम किसी स्ट्रिंग को Date ऑब्जेक्ट की तरह व्यवहार करने का प्रयास करते हैं तो हमारा एप्लिकेशन क्रैश हो सकता है। उदाहरण के लिए, यदि हम comment.updatedAt.toLocaleDateString() कॉल करते हैं तो यह टूट सकता है।


दरअसल, हमारे छोटे उदाहरण में, हम केवल Date ऑब्जेक्ट को संख्यात्मक टाइमस्टैम्प से बदल सकते हैं, जो JSON क्रमबद्धता के लिए अच्छा काम करता है। हालाँकि, वास्तविक अनुप्रयोगों में, डेटा ऑब्जेक्ट व्यापक हो सकते हैं, प्रकारों को कई स्थानों पर परिभाषित किया जा सकता है, और विकास के दौरान ऐसी त्रुटि की पहचान करना एक चुनौतीपूर्ण कार्य हो सकता है।


क्या होगा यदि हम JSON के बारे में टाइपस्क्रिप्ट की समझ को बढ़ा सकें?


क्रमांकन से निपटना

आरंभ करने के लिए, आइए जानें कि टाइपस्क्रिप्ट को कैसे समझा जाए कि कौन से डेटा प्रकारों को JSON में सुरक्षित रूप से क्रमबद्ध किया जा सकता है। मान लीजिए कि हम एक फ़ंक्शन safeJsonStringify बनाना चाहते हैं, जहां टाइपस्क्रिप्ट यह सुनिश्चित करने के लिए इनपुट डेटा प्रारूप की जांच करेगा कि यह JSON क्रमबद्ध है।


 function safeJsonStringify(data: JSONValue) { return JSON.stringify(data); }


इस फ़ंक्शन में, सबसे महत्वपूर्ण हिस्सा JSONValue प्रकार है, जो सभी संभावित मानों का प्रतिनिधित्व करता है जिन्हें JSON प्रारूप में दर्शाया जा सकता है। कार्यान्वयन काफी सीधा है:


 type JSONPrimitive = string | number | boolean | null | undefined; type JSONValue = JSONPrimitive | JSONValue[] | { [key: string]: JSONValue; };


सबसे पहले, हम JSONPrimitive प्रकार को परिभाषित करते हैं, जो सभी आदिम JSON डेटा प्रकारों का वर्णन करता है। हम इस तथ्य के आधार पर undefined प्रकार को भी शामिल करते हैं कि क्रमबद्ध होने पर, undefined मान वाली कुंजियाँ छोड़ दी जाएंगी। डिसेरिएलाइज़ेशन के दौरान, ये कुंजियाँ ऑब्जेक्ट में दिखाई नहीं देंगी, जो कि ज्यादातर मामलों में एक ही बात है।


आगे, हम JSONValue प्रकार का वर्णन करते हैं। यह प्रकार पुनरावर्ती प्रकारों का वर्णन करने के लिए टाइपस्क्रिप्ट की क्षमता का उपयोग करता है, जो ऐसे प्रकार हैं जो स्वयं को संदर्भित करते हैं। यहां, JSONValue या तो JSONPrimitive हो सकता है, JSONValue की एक सरणी हो सकती है, या एक ऑब्जेक्ट हो सकता है जहां सभी मान JSONValue प्रकार के हों। परिणामस्वरूप, इस प्रकार के JSONValue के एक वेरिएबल में असीमित नेस्टिंग वाले ऐरे और ऑब्जेक्ट हो सकते हैं। JSON प्रारूप के साथ संगतता के लिए इनके भीतर के मूल्यों की भी जाँच की जाएगी।


अब हम निम्नलिखित उदाहरणों का उपयोग करके अपने safeJsonStringify फ़ंक्शन का परीक्षण कर सकते हैं:


 // No errors safeJsonStringify({ updatedAt: Date.now() }); // Yields an error: // Argument of type '{ updatedAt: Date; }' is not assignable to parameter of type 'JSONValue'. // Types of property 'updatedAt' are incompatible. // Type 'Date' is not assignable to type 'JSONValue'. safeJsonStringify({ updatedAt: new Date(); });


ऐसा लगता है कि सब कुछ ठीक से काम कर रहा है। फ़ंक्शन हमें दिनांक को एक संख्या के रूप में पास करने की अनुमति देता है लेकिन यदि हम Date ऑब्जेक्ट पास करते हैं तो एक त्रुटि उत्पन्न होती है।


लेकिन आइए एक अधिक यथार्थवादी उदाहरण पर विचार करें, जिसमें फ़ंक्शन को दिया गया डेटा एक वेरिएबल में संग्रहीत होता है और उसका एक वर्णित प्रकार होता है।


 interface PostComment { authorId: string; text: string; updatedAt: number; }; const comment: PostComment = {...}; // Yields an error: // Argument of type 'PostComment' is not assignable to parameter of type 'JSONValue'. // Type 'PostComment' is not assignable to type '{ [key: string]: JSONValue; }'. // Index signature for type 'string' is missing in type 'PostComment'. safeJsonStringify(comment);


अब, चीजें थोड़ी मुश्किल हो रही हैं। टाइपस्क्रिप्ट हमें JSONValue प्रकार के फ़ंक्शन पैरामीटर के लिए PostComment प्रकार का एक वैरिएबल असाइन नहीं करने देगा, क्योंकि "प्रकार 'स्ट्रिंग' के लिए सूचकांक हस्ताक्षर 'PostComment' प्रकार में गायब है"।


तो, सूचकांक हस्ताक्षर क्या है और यह गायब क्यों है? याद रखें कि हमने उन वस्तुओं का वर्णन कैसे किया था जिन्हें JSON प्रारूप में क्रमबद्ध किया जा सकता है?


 type JSONValue = { [key: string]: JSONValue; };


इस मामले में, [key: string] सूचकांक हस्ताक्षर है। इसका अर्थ है "इस ऑब्जेक्ट में स्ट्रिंग्स के रूप में कोई भी कुंजी हो सकती है, जिसके मान में JSONValue प्रकार होता है"। तो, यह पता चला है कि हमें PostComment प्रकार में एक इंडेक्स हस्ताक्षर जोड़ने की आवश्यकता है, है ना?


 interface PostComment { authorId: string; text: string; updatedAt: number; // Don't do this: [key: string]: JSONValue; };


ऐसा करने का मतलब यह होगा कि टिप्पणी में कोई भी मनमाना फ़ील्ड हो सकता है, जो आमतौर पर किसी एप्लिकेशन में डेटा प्रकारों को परिभाषित करते समय वांछित परिणाम नहीं होता है।


इंडेक्स हस्ताक्षर के साथ समस्या का वास्तविक समाधान मैप किए गए प्रकारों से आता है, जो फ़ील्ड पर पुनरावर्ती रूप से पुनरावृत्ति करने की अनुमति देता है, यहां तक कि उन प्रकारों के लिए भी जिनके पास इंडेक्स हस्ताक्षर परिभाषित नहीं है। जेनेरिक के साथ संयुक्त, यह सुविधा किसी भी डेटा प्रकार T दूसरे प्रकार JSONCompatible<T> में परिवर्तित करने की अनुमति देती है, जो JSON प्रारूप के साथ संगत है।


 type JSONCompatible<T> = unknown extends T ? never : { [P in keyof T]: T[P] extends JSONValue ? T[P] : T[P] extends NotAssignableToJson ? never : JSONCompatible<T[P]>; }; type NotAssignableToJson = | bigint | symbol | Function;


JSONCompatible<T> प्रकार एक मैप किया गया प्रकार है जो निरीक्षण करता है कि किसी दिए गए प्रकार T JSON में सुरक्षित रूप से क्रमबद्ध किया जा सकता है या नहीं। यह टाइप T में प्रत्येक संपत्ति पर पुनरावृत्ति करके और निम्नलिखित कार्य करके ऐसा करता है:


  1. T[P] extends JSONValue ? T[P] : ... सशर्त प्रकार सत्यापित करता है कि संपत्ति का प्रकार JSONValue प्रकार के साथ संगत है, यह सुनिश्चित करते हुए कि इसे सुरक्षित रूप से JSON में परिवर्तित किया जा सकता है। जब यह मामला होता है, तो संपत्ति का प्रकार अपरिवर्तित रहता है।
  2. T[P] extends NotAssignableToJson ? never : ... सशर्त प्रकार सत्यापित करता है कि क्या संपत्ति का प्रकार JSON को असाइन करने योग्य नहीं है। इस मामले में, संपत्ति के प्रकार को never में बदल दिया जाता है, जिससे संपत्ति अंतिम प्रकार से प्रभावी रूप से फ़िल्टर हो जाती है।
  3. यदि इनमें से कोई भी शर्त पूरी नहीं होती है, तो निष्कर्ष निकाले जाने तक प्रकार की पुनरावर्ती जाँच की जाती है। इस तरह यह तब भी काम करता है, जब टाइप में इंडेक्स हस्ताक्षर न हो।


unknown extends T ? never :... शुरुआत में चेक का उपयोग unknown प्रकार को खाली ऑब्जेक्ट प्रकार {} में परिवर्तित होने से रोकने के लिए किया जाता है, जो अनिवार्य रूप से any प्रकार के बराबर है।


एक और दिलचस्प पहलू NotAssignableToJson प्रकार है। इसमें दो टाइपस्क्रिप्ट प्रिमिटिव (बिगिंट और सिंबल) और Function प्रकार शामिल हैं, जो किसी भी संभावित फ़ंक्शन का वर्णन करता है। Function प्रकार किसी भी मान को फ़िल्टर करने में महत्वपूर्ण है जो JSON को असाइन करने योग्य नहीं है। ऐसा इसलिए है क्योंकि जावास्क्रिप्ट में कोई भी जटिल ऑब्जेक्ट ऑब्जेक्ट प्रकार पर आधारित होता है और इसकी प्रोटोटाइप श्रृंखला में कम से कम एक फ़ंक्शन होता है (उदाहरण के लिए, toString() )। JSONCompatible प्रकार उन सभी फ़ंक्शंस पर पुनरावृत्त होता है, इसलिए फ़ंक्शंस की जाँच करना किसी भी चीज़ को फ़िल्टर करने के लिए पर्याप्त है जो JSON के लिए क्रमबद्ध नहीं है।


अब, आइए क्रमबद्धता फ़ंक्शन में इस प्रकार का उपयोग करें:


 function safeJsonStringify<T>(data: JSONCompatible<T>) { return JSON.stringify(data); }


अब, फ़ंक्शन एक सामान्य पैरामीटर T का उपयोग करता है और JSONCompatible<T> तर्क को स्वीकार करता है। इसका मतलब यह है कि यह T प्रकार का तर्क data लेता है, जो JSON-संगत प्रकार होना चाहिए। अब हम इंडेक्स हस्ताक्षर के बिना डेटा प्रकारों के साथ फ़ंक्शन का उपयोग कर सकते हैं।


फ़ंक्शन अब एक सामान्य पैरामीटर T का उपयोग करता है जो JSONCompatible<T> प्रकार से विस्तारित होता है। इसका मतलब यह है कि यह T प्रकार के तर्क data स्वीकार करता है, जो JSON-संगत प्रकार होना चाहिए। परिणामस्वरूप, हम फ़ंक्शन का उपयोग उन डेटा प्रकारों के साथ कर सकते हैं जिनमें इंडेक्स हस्ताक्षर की कमी है।


 interface PostComment { authorId: string; text: string; updatedAt: number; } function saveComment(comment: PostComment) { const serializedComment = safeJsonStringify(comment); localStorage.setItem('draft', serializedComment); }


इस दृष्टिकोण का उपयोग तब किया जा सकता है जब JSON क्रमांकन आवश्यक हो, जैसे कि नेटवर्क पर डेटा स्थानांतरित करना, HTML में डेटा एम्बेड करना, लोकलस्टोरेज में डेटा संग्रहीत करना, श्रमिकों के बीच डेटा स्थानांतरित करना आदि। इसके अतिरिक्त, toJsonValue हेल्पर का उपयोग तब किया जा सकता है जब कोई सख्ती से टाइप की गई वस्तु हो एक सूचकांक हस्ताक्षर को JSONValue प्रकार के एक चर को निर्दिष्ट करने की आवश्यकता है।


 function toJsonValue<T>(value: JSONCompatible<T>): JSONValue { return value; } const comment: PostComment = {...}; const data: JSONValue = { comment: toJsonValue(comment) };


इस उदाहरण में, toJsonValue उपयोग करने से हमें PostComment प्रकार में अनुपलब्ध इंडेक्स हस्ताक्षर से संबंधित त्रुटि को बायपास करने की सुविधा मिलती है।


अक्रमांकन से निपटना

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


टाइपस्क्रिप्ट के टाइप सिस्टम के दृष्टिकोण से, चुनौती काफी सरल है। आइए निम्नलिखित उदाहरण पर विचार करें:


 function safeJsonParse(text: string) { return JSON.parse(text) as unknown; } const data = JSON.parse(text); // ^? unknown


इस उदाहरण में, हम any रिटर्न प्रकार को unknown प्रकार से प्रतिस्थापित कर रहे हैं। unknown क्यों चुनें? अनिवार्य रूप से, एक JSON स्ट्रिंग में कुछ भी हो सकता है, न कि केवल वह डेटा जो हम प्राप्त करने की अपेक्षा करते हैं। उदाहरण के लिए, डेटा प्रारूप विभिन्न एप्लिकेशन संस्करणों के बीच बदल सकता है या ऐप का दूसरा भाग उसी लोकलस्टोरेज कुंजी पर डेटा लिख सकता है। इसलिए, unknown सबसे सुरक्षित और सबसे सटीक विकल्प है।


हालाँकि, unknown प्रकार के साथ काम करना केवल वांछित डेटा प्रकार निर्दिष्ट करने से कम सुविधाजनक है। टाइप-कास्टिंग के अलावा, unknown प्रकार को आवश्यक डेटा प्रकार में बदलने के कई तरीके हैं। ऐसी ही एक विधि रनटाइम पर डेटा को सत्यापित करने और डेटा अमान्य होने पर विस्तृत त्रुटियों को फेंकने के लिए सुपरस्ट्रक्चर लाइब्रेरी का उपयोग कर रही है।


 import { create, object, number, string } from 'superstruct'; const PostComment = object({ authorId: string(), text: string(), updatedAt: number(), }); // Note: we no longer need to manually specify the return type function restoreDraft() { const text = localStorage.getItem('draft'); return text ? create(JSON.parse(text), PostComment) : undefined; }


यहां, create फ़ंक्शन एक प्रकार के गार्ड के रूप में कार्य करता है, जो प्रकार को वांछित Comment इंटरफ़ेस तक सीमित करता है । परिणामस्वरूप, अब हमें रिटर्न प्रकार को मैन्युअल रूप से निर्दिष्ट करने की आवश्यकता नहीं है।


एक सुरक्षित डिसेरिएलाइज़ेशन विकल्प लागू करना केवल आधी कहानी है। यह भी उतना ही महत्वपूर्ण है कि प्रोजेक्ट में अगला कार्य निपटाते समय इसका उपयोग करना न भूलें। यह विशेष रूप से चुनौतीपूर्ण हो जाता है यदि एक बड़ी टीम परियोजना पर काम कर रही है, क्योंकि सभी समझौतों और सर्वोत्तम प्रथाओं का पालन सुनिश्चित करना मुश्किल हो सकता है।


टाइपस्क्रिप्ट-एसलिंट इस कार्य में सहायता कर सकता है। यह उपकरण any असुरक्षित उपयोग के सभी उदाहरणों की पहचान करने में मदद करता है। विशेष रूप से, JSON.parse के सभी उपयोग पाए जा सकते हैं और यह सुनिश्चित किया जा सकता है कि प्राप्त डेटा का प्रारूप जांचा गया है। कोडबेस में any प्रकार से छुटकारा पाने के बारे में अधिक जानकारी मेकिंग टाइपस्क्रिप्ट को सचमुच "मज़बूती से टाइप किया गया" लेख में पढ़ा जा सकता है।


निष्कर्ष

सुरक्षित JSON क्रमबद्धता और डिसेरिएलाइज़ेशन में सहायता के लिए डिज़ाइन किए गए अंतिम उपयोगिता फ़ंक्शन और प्रकार यहां दिए गए हैं। आप इनका परीक्षण तैयार टीएस प्लेग्राउंड में कर सकते हैं।


 type JSONPrimitive = string | number | boolean | null | undefined; type JSONValue = JSONPrimitive | JSONValue[] | { [key: string]: JSONValue; }; type NotAssignableToJson = | bigint | symbol | Function; type JSONCompatible<T> = unknown extends T ? never : { [P in keyof T]: T[P] extends JSONValue ? T[P] : T[P] extends NotAssignableToJson ? never : JSONCompatible<T[P]>; }; function toJsonValue<T>(value: JSONCompatible<T>): JSONValue { return value; } function safeJsonStringify<T>(data: JSONCompatible<T>) { return JSON.stringify(data); } function safeJsonParse(text: string): unknown { return JSON.parse(text); }


इनका उपयोग किसी भी स्थिति में किया जा सकता है जहां JSON क्रमबद्धता आवश्यक है।


मैं कई वर्षों से अपनी परियोजनाओं में इस रणनीति का उपयोग कर रहा हूं, और इसने एप्लिकेशन विकास के दौरान संभावित त्रुटियों का तुरंत पता लगाकर अपनी प्रभावशीलता का प्रदर्शन किया है।


मुझे आशा है कि इस लेख ने आपको कुछ ताज़ा अंतर्दृष्टि प्रदान की है। पढ़ने के लिए आपका शुक्रिया!

उपयोगी कड़ियां