JSON থেকে CSV: হেডার কোথা থেকে আসে এবং মান কেন বদলে যায়
সংক্ষেপে: একটি সত্যিকারের কনভার্টারে নয়টি পরীক্ষা: "প্রথম রেকর্ডের কী" কৌশল নিঃশব্দে ফিল্ড হারায়, null, অনুপস্থিতি ও খালি স্ট্রিং সব খালি সেলে পরিণত হয়, ২০ ডিজিটের পূর্ণসংখ্যা JSON.parse-এই নির্ভুলতা হারায়, আর কী-তে একটি ডট দুটি ভিন্ন পথকে একই কলামে সংঘর্ষে ফেলে। হেডার কীভাবে তৈরি হয়, কোন মানগুলো বিকৃত হয় এবং নিচের প্রান্তের ব্যবহার অনুযায়ী কৌশল কীভাবে বেছে নেবেন।

JSON থেকে CSV রূপান্তরে যখন ডেটা হারায়, প্রায় কখনোই তা "রূপান্তর" ধাপে ঘটে না। ক্ষতি ঘটে এমন দুটি প্রশ্নে, যা কেউ করেনি: হেডার কে ঠিক করল, এবং প্রতিটি সেল কি সেই মান হিসেবেই চেনা যায়, যা সে ছিল?
ত্রিশ সেকেন্ডে একটি ফিল্ড হারিয়ে যায়
অনেক কনভার্টার — প্রচলিত jq পাইপলাইনসহ — হেডার নেয় শুধু প্রথম রেকর্ড থেকে। Python-এ ফলাফল পুনরায় তৈরি করুন:
data = [{"id": 1, "name": "A"}, {"id": 2, "email": "b@x.com"}]
first_keys = list(data[0].keys()) # ['id', 'name']
rows = [[r.get(k, "") for k in first_keys] for r in data]
# দ্বিতীয় রেকর্ডের email কোনো ত্রুটি বা সতর্কতা ছাড়াই হারিয়ে যায়
email কীটি হেডারে নেই: এর এমনকি খালি সেল হওয়ারও অধিকার নেই — কলামটি কখনোই তৈরিই হয়নি, আর মানটি নিঃশব্দে ফেলে দেওয়া হয়। সমাধান হলো কী ইউনিয়ন: সব রেকর্ড ঘুরে দেখা, যেকোনো জায়গায় আবির্ভূত প্রতিটি কী-এর জন্য কলাম বানানো। একই ডেটা ইউনিয়নের মধ্য দিয়ে গেলে হেডার হয় id,name,email, যেখানে name অনুপস্থিত সেখানে খালি সেল — আর কিছুই হারায় না।
আমাদের /convert/csv পেজের সরাসরি কনভার্টার ঠিক এই কৌশলই প্রয়োগ করে। [{"id":1,"name":"A"},{"id":2,"email":"b@x.com"}] ইনপুট দিলে এটি দেয়:
id,name,email
1,A,
2,,b@x.com
কীগুলো প্রথম আবির্ভাবের ক্রমে সাজানো হয়, আর অনুপস্থিত ফিল্ড খালি সেলে পরিণত হয়। jsonoperations ও jsonbeam দুজনেই একে বলে "ক্লাসিক বাগের সংশোধন" — কিন্তু "ইউনিয়ন ব্যবহার করে" গল্পের অর্ধেক মাত্র। ইউনিয়নের নিজস্ব মূল্য আছে; সেটাই পরের অনুচ্ছেদ।
হেডার বানানোর তিনটি পথ — এবং কার জন্য কোনটি
| হেডার কৌশল | কীভাবে তৈরি হয় | শক্তি | মূল্য |
|---|---|---|---|
| প্রথম রেকর্ডের কী | শুধু প্রথম রেকর্ড পড়া হয় | কম কলাম, স্থিতিশীল, jq-র এক লাইন | পরের রেকর্ডের নতুন ফিল্ড নিঃশব্দে হারায় |
| কী ইউনিয়ন | সব রেকর্ড পড়া হয় | কোনো ফিল্ড হারায় না | গড়ন ভিন্ন হলে বিরল কলাম ও অনেক খালি |
| সুনির্দিষ্ট কলাম তালিকা | হাতে বা স্কিমা থেকে সংজ্ঞায়িত | সম্পূর্ণ নিয়ন্ত্রণযোগ্য, পুনরুৎপাদনযোগ্য | রক্ষণাবেক্ষণ লাগে; নতুন ফিল্ডে হাতে হালনাগাদ চাই |
নির্বাচনের মানদণ্ড "কোনটি অধিক পরিশীলিত" নয়, বরং গ্রাহক এই CSV দিয়ে কী করবে:
- একবার উঁকি দেওয়া সহকর্মী: প্রথম রেকর্ডের কী-ই যথেষ্ট, রেকর্ডের গড়ন সাধারণত অভিন্ন।
- রিপোর্ট বা ডেটা পাইপলাইন: কী ইউনিয়ন ন্যূনতম শর্ত — খালি সেল নিজেই তথ্য।
- নিয়মিত স্বয়ংক্রিয় এক্সপোর্ট: সুনির্দিষ্ট কলাম তালিকা। গড়নের ড্রিফট (উপরের প্রান্তে নতুন ফিল্ড, নাম বদল) জোরে ব্যর্থ হবে, নিঃশব্দে এমন টেবিল বানাবে না, যার ভুল কেউই নজরে আনে না।
তৃতীয় বিকল্পটিই সবচেয়ে অবহেলিত। অটোমেশনে "রূপান্তর সফল হয়েছে" এবং "রূপান্তর সঠিক" দুটি ভিন্ন বক্তব্য — আর সুনির্দিষ্ট কলাম তালিকাই একমাত্র প্রক্রিয়া, যা দুটিকে আলাদা করে।
কী-তে ডট থাকলে ফ্ল্যাটেনিং-এর পথ সংঘর্ষে যায়
ডট নোটেশনে ফ্ল্যাটেনিং {"a": {"b": 2}}-কে a.b কলামে বদলায়। কিন্তু কীটি নিজেই ডট ধারণ করলে? pandas-এ পরীক্ষিত:
import pandas as pd
d = [{"a.b": 1, "a": {"b": 2}}]
pd.json_normalize(d, sep=".")
# একটি মাত্র কলাম বেঁচে থাকে: ['a.b'] — দুটি সম্পূর্ণ ভিন্ন কী একই পথে সংঘর্ষে যায়
আক্ষরিক কী a.b এবং নেস্টেড পথ a → b একই কলাম নাম তৈরি করে, একটি আরেকটি মুছে দেয়, আর ডেটা নিঃশব্দে ক্ষতিগ্রস্ত হয়। উদাহরণটি নির্মিত, কিন্তু সত্যিকারের ডেটাতেও ঘটে: মনিটরিং মেট্রিকের নাম (cpu.usage) আর লগ ফিল্ড (error.type) ডট ভালোবাসে।
দুটি প্রতিকার:
- সেপারেটর বদলান।
sep="_"বা নিজের সেপারেটর — মূল কীগুলো কখনো তা ধারণ না করে যাচাই করার পর। - পথ নোটেশন বদলান। JSON Pointer সেপারেটর হিসেবে
/ব্যবহার করে (/a/b); toolvore-এর Excel কনভার্টার এই পথেই যায়। এরও একই অস্পষ্টতার শ্রেণি —/-সহ কী~1লিখতে হয় — তবে এস্কেপ নিয়মগুলো RFC 6901-এ মানবদ্ধ, প্রতিটি টুলের নিজস্ব উদ্ভাবন নয়।
যাই বেছে নিন, নিয়মটি এক্সপোর্টের ডকুমেন্টেশনে লিখে রাখুন। CSV হেডার-ই এই ডেটা বহন করা একমাত্র "স্কিমা"; পাঠক আপনার ফ্ল্যাটেনিং নিয়ম অন্য কোথাও খুঁজে পাবে না।
অ্যারে: তিনটি কৌশল, প্রতিটি ভিন্ন কিছু হারায়
JSON থেকে CSV-র পথে অ্যারেই আসল বাধা: একটি সেল একাধিক মান ধরে রাখতে পারে না। তিনটি কৌশল, বাস্তবে যাচাই করা ক্ষতিসহ:
| কৌশল | আউটপুটের রূপ | কী হারায় | যার জন্য আদর্শ |
|---|---|---|---|
| সেলে JSON | অ্যারে JSON স্ট্রিং হিসেবে সেলে সিরিয়ালাইজড | টেবিল টুল উপাদান ফিল্টার বা অ্যাগ্রিগেট করতে পারে না | আগে বিশ্বস্ততা, হাতে পরীক্ষা |
| ইনডেক্সড কলাম | tags.0, tags.1, প্রতি একটি কলাম |
দীর্ঘতম অ্যারের সঙ্গে কলাম সংখ্যা বিস্ফোরিত; অনেক খালি | নির্দিষ্ট দৈর্ঘ্যের ছোট অ্যারে |
| বিস্ফোরিত রো | প্রতি উপাদানে এক রো, প্যারেন্ট ফিল্ড ডুপ্লিকেট | রো-স্ফীতি; খালি অ্যারের আচরণ আলাদাভাবে ঠিক করতে হয় | বিস্তারিত বিশ্লেষণ, উপাদানভিত্তিক অ্যাগ্রিগেশন |
আমাদের /convert/csv ও /convert/table কনভার্টার সেলে JSON ব্যবহার করে। [{"orderId":"A001","items":[{"sku":"X1","qty":2},{"sku":"Y9","qty":1}]}] ইনপুট দেয়:
orderId,items
A001,"[{""sku"":""X1"",""qty"":2},{""sku"":""Y9"",""qty"":1}]"
বিস্ফোরিত রো দরকার হলে pandas record_path সরাসরি তা করে:
orders = [{"orderId": "A001", "total": 99,
"items": [{"sku": "X1", "qty": 2}, {"sku": "Y9", "qty": 1}]}]
items = pd.json_normalize(orders, record_path=["items"],
meta=["orderId", "total"], sep=".")
# দুটি বিস্তারিত রো; প্রতিটিতে orderId ও total ডুপ্লিকেট
বিস্ফোরণে তিনটি ফাঁদ আগেই ঠিক করতে হয়: একাধিক অ্যারে একসঙ্গে বিস্ফোরণ কার্টেসিয়ান প্রোডাক্ট গড়ে; খালি অ্যারে ডিফল্টে কোনো রো দেয় না (আপনার গ্রাহক তালিকা মানুষ হারায়); আর ডুপ্লিকেট প্যারেন্ট ফিল্ডে নিচের প্রান্তে orderId দিয়ে অ্যাগ্রিগেট করার সময় ডিডুপ্লিকেশন লাগে। convertcsvonline গাইড "আগে গ্র্যানুলারিটি ঠিক করুন" কথাটি এজন্যই প্রথম ধাপে রাখে — গ্র্যানুলারিটি স্থির হলে তিনটি প্রশ্নেরই একটি করে উত্তর।
পরীক্ষিত: রূপান্তর থেকে বদলে যে মান বেরোয়
আমাদের সত্যিকারের কনভার্টারে বিশ্বস্ততার পরীক্ষা (Node v22.13.1 + papaparse, /convert/csv-এর সেই একই বাস্তবায়ন):
১. null, অনুপস্থিতি ও খালি স্ট্রিং সব খালি সেলে পরিণত হয়। ইনপুট [{"a":null},{"b":""},{"c":1}] — তিনটি সেলই খালি হয়ে বেরোয়। CSV-র কাছে তিনটি অবস্থা নেই, শুধু "অক্ষর আছে" ও "অক্ষর নেই"। রাউন্ড-ট্রিপের পর তিনটিই "" হিসেবে পড়া যায়; মূল সিমান্টিক্স অপুনরুদ্ধারযোগ্য।
২. নেস্টেড অবজেক্ট ও অ্যারে JSON স্ট্রিং হয়ে যায়। RFC 4180 অনুযায়ী কোট ডাবল হয় ({""name"":""张三""}), আর সেলটি যেকোনো JSON পার্সারে দিলেই মান ফিরে আসে — তাই সেলে JSON বিশ্বস্ততা রক্ষা করে।
৩. ২০ ডিজিটের পূর্ণসংখ্যা পার্সিং-এই নির্ভুলতা হারায় — দোষ CSV-র নয়। ইনপুট "id": 9007199254740993; CSV-তে ইতিমধ্যেই 9007199254740992। JavaScript-এর JSON.parse IEEE 754 double ব্যবহার করে: ক্ষতি ঘটে পার্সিং-এ, যেকোনো CSV কনভার্টারের আগে। Python json.load সম্পূর্ণ সংরক্ষণ করে। আইডি-জাতীয় ফিল্ড (snowflake ID, অর্ডার নম্বর) উৎপাদকের কাছ থেকেই স্ট্রিং হয়ে আসা উচিত। সংখ্যার নির্ভুলতা নিয়ে আরও দেখুন বড় সংখ্যা কেন নির্ভুলতা হারায়।
৪. স্ট্রিং-এর শুরুর শূন্য CSV-র টেক্সট স্তরে নিরাপদ। "phone": "00123" CSV-তে 00123-ই থাকে, কারণ শুরু থেকে শেষ পর্যন্ত এটি স্ট্রিং এবং কিছুই তাকে ছুঁতে পারে না। শূন্য ছুঁয়ে ফেলে পরের ধাপ, যেখানে Excel সেলকে সংখ্যা ধরে — দেখুন JSON থেকে Excel-এ রূপান্তরের চার পথ।
৫. কমা, কোট বা নিউলাইন-সহ ফিল্ড। papaparse RFC 4180 মানে: ফিল্ড কোট করা, ভেতরের কোট ডাবল করা। "He said \"hi\", ok" বেরোয় "He said ""hi"", ok" হয়ে এবং রাউন্ড-ট্রিপে অক্ষত থাকে। লাইব্রেরি এড়িয়ে হাতে লেখা সিরিয়ালাইজার প্রায় সবসময় ঠিক সেখানেই ব্যর্থ হয়।
রাউন্ড-ট্রিপ: ইনফারেন্স সুইচ কী বদলায়
CSV → JSON-এ প্রতিটি সেলের টাইপ অনুমান করতে হয়। আমাদের কনভার্টার টাইপ ইনফারেন্স ডিফল্টে বন্ধ রাখে এবং সব স্ট্রিং হিসেবে ফেরত দেয়:
[{ "id": "9007199254740992", "phone": "00123" }]
typeInference চালু থাকলে id সংখ্যা হয়ে যায় (নির্ভুলতা তখন হারানোই), আর phone স্ট্রিং থাকে — ইনফারেন্স নিয়ম শুরুর শূন্যের রূপ যেমন 010 বাদ দেয়, যা বৈধ সংখ্যা লিটারাল নয়, তাই স্ট্রিংটি ভাগ্যে বেঁচে যায়।
এই অসমতা মনে রাখুন: ইনফারেন্স বন্ধ সবচেয়ে খারাপে টাইপ কেড়ে নেয়; ইনফারেন্স চালু ডেটা কেড়ে নিতে পারে। সংখ্যা দরকার এমন কলাম সুনির্দিষ্ট কলাম সংজ্ঞা দিয়ে রূপান্তর করুন, গ্লোবাল ইনফারেন্সের ভরসায় নয়। আমাদের আগের নিবন্ধ JSON, CSV ও টেবিলার ডেটা-র rowsToRecords বাস্তবায়ন ঠিক এই নীতিই কোডে।
গ্রাহক অনুযায়ী কৌশল নির্বাচন: সিদ্ধান্ত টেবিল
| কে CSV পায় | হেডার কৌশল | অ্যারে ব্যবস্থাপনা | টাইপ |
|---|---|---|---|
| একবার খুলবে এমন সহকর্মী | কী ইউনিয়ন | সেলে JSON | ইনফারেন্স ছাড়া |
| Excel / রিপোর্টিং | কী ইউনিয়ন | সেলে JSON, বা নির্দিষ্ট দৈর্ঘ্যে ইনডেক্সড কলাম | আইডি কলাম জোরে টেক্সট |
| ডেটাবেস ইমপোর্ট | সুনির্দিষ্ট কলাম তালিকা | আলাদা master/detail টেবিল, বা বিস্ফোরণ | সুনির্দিষ্ট কলাম সংজ্ঞা |
| BI / অ্যাগ্রিগেশন | সুনির্দিষ্ট কলাম তালিকা | বিস্ফোরিত রো, গ্র্যানুলারিটি = ক্ষুদ্রতম বিশ্লেষণ একক | সুনির্দিষ্ট কলাম সংজ্ঞা |
| স্বয়ংক্রিয় পাইপলাইন | সুনির্দিষ্ট কলাম তালিকা | সেলে JSON, নিচের প্রান্তে বিস্ফোরণ | ইনফারেন্স বন্ধ, কলাম-ধারে রূপান্তর |
সাধারণ নিয়ম: মানুষের কাছে যত নিকটে, তত শিথিল করার সুযোগ; মেশিনের কাছে যত নিকটে, তত সবকিছু সুনির্দিষ্ট হতে হবে।
কপি করার মতো চেকলিস্ট
- [ ] হেডার কি সম্পূর্ণ কী ইউনিয়ন থেকে, নাকি সুনির্দিষ্ট তালিকা থেকে? না হলে, প্রমাণ করতে পারেন কোনো রেকর্ড ফিল্ড হারায়নি?
- [ ] কী-তে ডট বা স্ল্যাশ থাকতে পারে কি? ফ্ল্যাটেনিং সেপারেটরের সঙ্গে সংঘর্ষ যাচাই করা হয়েছে?
- [ ] অ্যারে কৌশল ডকুমেন্টেড কি? খালি অ্যারের আচরণ স্থির করা হয়েছে?
- [ ]
null, অনুপস্থিতি ও খালি স্ট্রিং-এর জন্য গ্রাহক কী পড়বে? একটি নিয়ম বেছে স্থির করুন। - [ ] আইডি-জাতীয় কলাম (ID, অর্ডার নম্বর, ফোন) শুরু থেকে শেষ পর্যন্ত স্ট্রিং হয়ে ভ্রমণ করে?
- [ ] বিস্ফোরণের পর রো সংখ্যা কি অ্যারে দৈর্ঘ্যের সমষ্টির সমান? নমুনায় যাচাই করা?
- [ ] কমা, কোট ও নিউলাইন-সহ উদাহরণ ফিল্ড রাউন্ড-ট্রিপে অক্ষত থাকে?
- [ ] রিইমপোর্টে গ্লোবাল টাইপ ইনফারেন্স বন্ধ, সংখ্যা কলামে সুনির্দিষ্ট সংজ্ঞা আছে?
আটটির প্রতিটির স্পষ্ট উত্তর থাকলেই এই CSV "সঠিক" — শুধু "সফলভাবে রূপান্তরিত" নয়।
FAQ
কনভার্টার ডিফল্টে নেস্টেড অবজেক্ট কলামে ফ্ল্যাট করে না কেন?
ফ্ল্যাটেনিং কলাম সংখ্যাকে নিয়ন্ত্রণের বাইরে নিয়ে যায়: গভীর নেস্টিং user.address.geo.lat জাতীয় নাম বানায়, আর কী ইউনিয়নের সঙ্গে মিলে ভিন্ন গড়নের একটিমাত্র রেকর্ডই কলামের স্তূপ যোগ করে। সেলের JSON স্ট্রিং বিশ্বস্ততা ও স্থিতিশীল কলাম সংখ্যা ধরে রাখে; দরকার হলে সুনির্দিষ্টভাবে ফ্ল্যাট করুন।
হেডারে ডুপ্লিকেট কলাম নাম থাকলে কী করব?
ফ্ল্যাটেনিং-এর পর দুটি ভিন্ন কী-এর সংঘর্ষ (যেমন a.b বনাম নেস্টেড a→b) উপরের প্রান্তেই মেটাতে হয়: সেপারেটর বদলান বা JSON Pointer-এ যান। একই অবজেক্টের ভেতরের ডুপ্লিকেটে কনভার্টার সাধারণত শেষ মান রাখে; হেডারের ডুপ্লিকেট বাস্তবায়নভেদে — papaparse-এর unparse ডিডুপ্লিকেট করে না, Excel খোলার সময় নিঃশব্দে মার্জ বা বাদ দেয়। এজন্যই চেকলিস্টে আছে।
বিস্ফোরণের পর ডুপ্লিকেট প্যারেন্ট ফিল্ড কি বাগ?
না — এটি নকশা। বিস্ফোরিত রো মানে বিস্তারিত রো, আর বিস্তারিত রো কী দিয়ে প্যারেন্ট রেকর্ডে পুনঃসংযুক্ত হতে পারবে। নিচের প্রান্তে প্যারেন্ট কী দিয়ে অ্যাগ্রিগেট করুন; আকার উদ্বেগজনক হলে বিস্তারিত ফাইল ও আলাদা master টেবিল এক্সপোর্ট করুন।
কীভাবে যাচাই করব রূপান্তরে কোনো ফিল্ড হারায়নি?
কী অডিট করুন: মূল JSON-এর সব কী-এর ইউনিয়ন বের করুন, তারপর CSV-র কলাম হেডারের সেট, এবং পার্থক্য করুন। অশূন্য পার্থক্য মানে হারানো ফিল্ড। ব্যাচ পাইপলাইনে এটি টেস্টে assert হিসেবে কোড করুন: ফিল্ড হারাতে শুরু করা যেকোনো কনভার্টার পরিবর্তন স্বয়ংক্রিয়ভাবে ব্যর্থ হবে।