JSON का CSV और सारणीबद्ध डेटा में तथा उससे रूपांतरण
मुख्य बात: CSV द्वि-आयामी है, JSON tree के आकार का। रूपांतरण में कठिन हिस्सा कोड नहीं — बल्कि पहले से तय करना है कि array को कैसे flatten करना है और प्रकार कैसे बचाने हैं।

सार: जब लोग निर्यात पढ़ेंगे तो dotted key और JSON-स्ट्रिंग किए गए array से flatten करें, जब डेटाबेस में लोड करना हो तो मूल तालिका और संबंधित तालिकाओं में बाँटें, और array को अतिरिक्त पंक्तियों में केवल pivot के लिए खोलें; बहाल करते समय वैश्विक अनुमान के बजाय स्पष्ट स्तंभ परिभाषाओं से रूपांतरित करें।
Tree को तालिका में flatten करने पर पदानुक्रम हमेशा खो जाता है; तालिका को tree में बहाल करने पर प्रकार हमेशा अनुमानित होते हैं। दोनों दिशाओं को अपने नियम पहले ही बताने होते हैं।
1. CSV वास्तव में क्या तय करता है
RFC 4180 केवल कुछ पृष्ठों का है और लोगों की धारणा से कम चीज़ें तय करता है:
- हर रिकॉर्ड एक पंक्ति लेता है, फ़ील्ड comma से अलग होते हैं;
- पंक्ति समाप्ति CRLF है, हालाँकि व्यवहार में
\nभी स्वीकार किया जाता है; - comma, दोहरा उद्धरण या newline वाला फ़ील्ड दोहरे उद्धरण में लिपटा होना चाहिए;
- फ़ील्ड के भीतर दोहरा उद्धरण दो दोहरे उद्धरणों
""के रूप में लिखा जाता है; - पहली पंक्ति header हो सकती है;
- कोई अक्षर एन्कोडिंग नहीं बताई गई, और प्रकार भी नहीं।
id,name,remark
1,张三,"喜欢 JSON,讨厌尾逗号"
2,李四,"他说:""这行有引号"""
3,王五,"多行
备注"
नियम 3 पर ध्यान दें: newline वाले फ़ील्ड वैध हैं, इसलिए वह भोला parser जो पहले \n और फिर , पर बाँटता है, पक्का गलत होगा।
2. JSON → CSV: पहले संरचना तय करें
Tree संरचना को द्वि-आयामी तालिका में flatten करने की केवल तीन रणनीतियाँ हैं:
रणनीति 1: फ़ील्ड flatten करें (सबसे आम)
{ "id": 1, "user": { "name": "张三", "city": "杭州" }, "tags": ["a", "b"] }
id,user.name,user.city,tags
1,张三,杭州,"[""a"",""b""]"
- Nested object की key को
.से जोड़ें; - Array को स्तंभ के रूप में व्यक्त नहीं किया जा सकता, इसलिए उन्हें सीधे
JSON.stringifyकरके सेल में भरें (जो अब भी वैध CSV फ़ील्ड है); - कमी: स्प्रेडशीट टूल उस array पर filter नहीं कर सकते, इसलिए दूसरी बार पार्स करना पड़ता है।
रणनीति 2: array को कई पंक्तियों में खोलें
id,user.name,tag
1,张三,a
1,张三,b
यह उन one-to-many संबंधों के लिए उपयुक्त है जहाँ downstream उपभोक्ता प्रति element समेकन करते हैं। कमी यह है कि मूल फ़ील्ड दोहराए जाते हैं और पंक्तियों की संख्या फूल जाती है।
रणनीति 3: कई तालिकाओं में बाँटें
एक मुख्य तालिका और id से जुड़ी संबंधित तालिकाएँ — सबसे साफ़ तरीका, पर यह "एकल निर्यात" से आगे बढ़कर डेटा मॉडलिंग जैसा दिखने लगता है।
कौन सी? मनुष्यों के लिए निर्यात में रणनीति 1, डेटाबेस में लोड करने के लिए रणनीति 3, और डेटा pivot करने के लिए रणनीति 2 इस्तेमाल करें।
3. CSV → JSON: प्रकार कैसे अनुमानित होते हैं
CSV में प्रकार नहीं हैं, इसलिए बहाली का हर चरण अनुमान है:
| सेल की सामग्री | संभावित अर्थ | सीधे संख्या में बदलने का जोखिम |
|---|---|---|
00123 |
कर्मचारी ID, पिन कोड | अग्रणी शून्य खो गए → 123 |
1.10 |
संस्करण संख्या, धनराशि | 1.1 बन जाता है |
1e5 |
पहचानकर्ता या वैज्ञानिक संकेतन | 100000 बन जाता है |
2026-10-30 |
तारीख या स्ट्रिंग | समय क्षेत्र और प्रारूप में अस्पष्टता |
13800138000 |
फ़ोन नंबर | 11 अंकों तक सुरक्षित, पर कुछ ID परिशुद्धता खोते हैं |
"" |
खाली स्ट्रिंग या null | शब्दार्थ की अस्पष्टता |
सुझाया डिफ़ॉल्ट: सब कुछ स्ट्रिंग मानें, और schema या स्पष्ट स्तंभ परिभाषा को तय करने दें कि कौन से स्तंभ संख्या या boolean बनेंगे। निर्यात करते समय "स्मार्ट अनुमान" सुहावना लगता है और आयात करते समय घटनाओं का स्रोत बन जाता है।
type ColumnType = 'string' | 'number' | 'boolean' | 'json';
/**
* Converts CSV rows into objects according to the column definitions.
* @param rows The raw row array; the first row is the header
* @param columnTypes Mapping from column name to type
* @returns The converted array of objects
*/
function rowsToRecords(
rows: string[][],
columnTypes: Record<string, ColumnType>,
): Array<Record<string, unknown>> {
const [headerRow, ...dataRows] = rows;
return dataRows.map((dataRow) => {
const record: Record<string, unknown> = {};
headerRow.forEach((columnName, columnIndex) => {
const rawValue = dataRow[columnIndex] ?? '';
const columnType = columnTypes[columnName] ?? 'string';
if (columnType === 'number') {
const numericValue = Number(rawValue);
record[columnName] = Number.isFinite(numericValue) ? numericValue : null;
return;
}
if (columnType === 'boolean') {
record[columnName] = rawValue === 'true' || rawValue === '1';
return;
}
if (columnType === 'json') {
try {
record[columnName] = JSON.parse(rawValue);
} catch {
record[columnName] = rawValue;
}
return;
}
record[columnName] = rawValue;
});
return record;
});
}
4. Excel से जुड़े तीन जाल
4.1 एन्कोडिंग
जब Excel बिना BOM वाली UTF-8 CSV खोलता है, चीनी पाठ गड़बड़ (mojibake) बन जाता है। निर्यात पर BOM (\uFEFF) जोड़ना सबसे सस्ता संगतता उपाय है:
const bom = '\uFEFF';
const csvContent = `${bom}${headerLine}\n${bodyLines.join('\n')}`;
4.2 लंबी संख्याएँ वैज्ञानिक संकेतन में बदल जाना
18-अंकीय राष्ट्रीय ID या 19-अंकीय ऑर्डर संख्या Excel में 1.23457E+17 दिखती है, और मान स्वयं पहले ही कट चुका होता है।
फ़ील्ड के आगे tab लगाने या उसे सूत्र के रूप में लिपटने (="1234...") से पाठ प्रारूप बचता है, पर सेल सूत्र बन जाता है।
अधिक सुरक्षित तरीका यह है कि उपयोगकर्ताओं को "import सुविधा इस्तेमाल करें और स्तंभ को पाठ के रूप में बताएँ" कहें, या निर्यात के साथ .xlsx भी दें।
4.3 delimiter पर locale का असर
कुछ यूरोपीय locale डिफ़ॉल्ट रूप से अर्धविराम ; को separator मानते हैं। क्षेत्रों के पार वितरित करते समय या तो CSV को comma पर स्थिर करें और यह बताएँ, या TSV (tab-separated) संस्करण दें — TSV locale के प्रति अधिक सहिष्णु है।
5. बड़ी फ़ाइलें संभालना
एक लाख से अधिक पंक्तियों वाली CSV को एक साथ स्मृति में न लोड करें:
import { createReadStream } from 'node:fs';
import { parse } from 'csv-parse';
const parser = createReadStream('orders.csv').pipe(
parse({ columns: true, skipEmptyLines: true, bom: true }),
);
for await (const record of parser) {
// Process one record at a time; memory usage is independent of file size
}
निर्यात दिशा भी वैसी ही चलती है: एक विशाल स्ट्रिंग जोड़ने के बजाय चलते-चलते स्ट्रीम में उत्पन्न करें और लिखें।
6. Round-trip संगति की स्वयं-जाँच
CSV round trip (JSON → CSV → JSON) लगभग कभी बिल्कुल वैसा नहीं होता, पर आप तय कर सकते हैं कौन सी हानियाँ स्वीकार्य हैं:
| जाँच | अपेक्षा |
|---|---|
| फ़ील्ड संख्या | बिल्कुल वही |
| स्ट्रिंग मान | बिल्कुल वही (अग्रणी शून्य सहित) |
| संख्या प्रकार | स्तंभ परिभाषा से तय, अनुमान से नहीं |
| Nested object | dotted path से बहाल हो सकने योग्य |
| Array | JSON स्ट्रिंग में serialize होने के बाद दिखने योग्य |
| खाली मान | null या खाली स्ट्रिंग में सामान्यीकृत — ठीक एक चुनें |
इस तालिका को परीक्षणों में डालना मौखिक सहमति से कहीं अधिक प्रभावी है।
7. चेकलिस्ट
- [ ] "एक पंक्ति एक रिकॉर्ड" का शब्दार्थ स्पष्ट बताया गया है और निर्यात फ़ाइल नाम में झलकता है;
- [ ] परिशुद्धता खो सकने वाला हर स्तंभ स्ट्रिंग के रूप में निर्यात होता है;
- [ ] चीनी CSV में BOM शामिल है, या एन्कोडिंग स्पष्ट बताई गई है;
- [ ] comma, उद्धरण या newline वाले फ़ील्ड ठीक escape होते हैं (लाइब्रेरी इस्तेमाल करें, स्वयं स्ट्रिंग न जोड़ें);
- [ ] बड़ी फ़ाइलें स्ट्रीमिंग पठन और लेखन से गुज़रती हैं;
- [ ] आयात स्तंभ परिभाषा के अनुसार प्रकार बदलता है, कभी वैश्विक स्मार्ट अनुमान से नहीं।
अक्सर पूछे जाने वाले प्रश्न
Flatten करना, पंक्तियाँ खोलना और तालिकाएँ बाँटना — इनमें से क्या चुनूँ?
मनुष्यों के लिए निर्यात में flatten करें: nested key को . से जोड़ें और array को JSON.stringify करके सेल में भरें, इस कीमत पर कि स्प्रेडशीट में उन array पर filter नहीं कर सकेंगे। जब लक्ष्य डेटाबेस हो, तो मूल तालिका और संबंधित तालिकाओं में बाँटें। जब downstream उपभोक्ता प्रति element समेकन करें, तो array को अतिरिक्त पंक्तियों में खोलें, मूल फ़ील्ड के दोहराव को स्वीकार करते हुए।
CSV आयात को स्वतः प्रकार अनुमान क्यों नहीं लगाना चाहिए?
RFC 4180 delimiter और escaping तय करता है पर प्रकार नहीं, इसलिए बहाली का हर चरण अनुमान है: 00123 अपने अग्रणी शून्य खो देता है, 1.10 1.1 बन जाता है, और 1e5 100000 बन जाता है। डिफ़ॉल्ट रूप से सब कुछ स्ट्रिंग मानें और schema या स्पष्ट स्तंभ परिभाषा को तय करने दें कि कौन से स्तंभ संख्या या boolean बनेंगे।
क्या CSV round trip से मूल JSON बिल्कुल वैसा ही मिलने की अपेक्षा करूँ?
लगभग कभी नहीं। इसके बजाय स्वीकार्य हानियाँ तय करें: वही फ़ील्ड संख्या, अग्रणी शून्य सहित वही स्ट्रिंग मान, स्तंभ परिभाषा से तय संख्या प्रकार, dotted path से बहाल हो सकने योग्य nested object, serialize होने के बाद दिखने योग्य array, और null या खाली स्ट्रिंग में सामान्यीकृत खाली मान — ठीक एक चुनें।