लगभग हर वेब एप्लिकेशन को डेटा क्रमांकन की आवश्यकता होती है। यह आवश्यकता निम्न स्थितियों में उत्पन्न होती है:
कई मामलों में, डेटा हानि या भ्रष्टाचार के गंभीर परिणाम हो सकते हैं, जिससे एक सुविधाजनक और सुरक्षित क्रमांकन तंत्र प्रदान करना आवश्यक हो जाता है जो विकास चरण के दौरान यथासंभव त्रुटियों का पता लगाने में मदद करता है। इन उद्देश्यों के लिए, डेटा ट्रांसफर प्रारूप के रूप में JSON और विकास के दौरान स्थिर कोड जाँच के लिए टाइपस्क्रिप्ट का उपयोग करना सुविधाजनक है।
टाइपस्क्रिप्ट जावास्क्रिप्ट के सुपरसेट के रूप में कार्य करता है, जो JSON.stringify
और JSON.parse
जैसे कार्यों के निर्बाध उपयोग को सक्षम करना चाहिए, है ना? पता चला, अपने सभी लाभों के बावजूद, टाइपस्क्रिप्ट स्वाभाविक रूप से यह नहीं समझता है कि 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
में प्रत्येक संपत्ति पर पुनरावृत्ति करके और निम्नलिखित कार्य करके ऐसा करता है:
T[P] extends JSONValue ? T[P] : ...
सशर्त प्रकार सत्यापित करता है कि संपत्ति का प्रकार JSONValue
प्रकार के साथ संगत है, यह सुनिश्चित करते हुए कि इसे सुरक्षित रूप से JSON में परिवर्तित किया जा सकता है। जब यह मामला होता है, तो संपत्ति का प्रकार अपरिवर्तित रहता है।T[P] extends NotAssignableToJson ? never : ...
सशर्त प्रकार सत्यापित करता है कि क्या संपत्ति का प्रकार JSON को असाइन करने योग्य नहीं है। इस मामले में, संपत्ति के प्रकार को never
में बदल दिया जाता है, जिससे संपत्ति अंतिम प्रकार से प्रभावी रूप से फ़िल्टर हो जाती है।
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 क्रमबद्धता आवश्यक है।
मैं कई वर्षों से अपनी परियोजनाओं में इस रणनीति का उपयोग कर रहा हूं, और इसने एप्लिकेशन विकास के दौरान संभावित त्रुटियों का तुरंत पता लगाकर अपनी प्रभावशीलता का प्रदर्शन किया है।
मुझे आशा है कि इस लेख ने आपको कुछ ताज़ा अंतर्दृष्टि प्रदान की है। पढ़ने के लिए आपका शुक्रिया!