JSON เป็น CSV: หัวคอลัมน์มาจากไหน และทำไมค่าต่าง ๆ จึงเปลี่ยนไป
สรุปสำคัญ: การทดลองเก้าครั้งบนตัวแปลงจริง: กลยุทธ์ "คีย์จากเรกคอร์ดแรก" หายฟิลด์เงียบ ๆ, null, การขาดหาย และสตริงว่างกลายเป็นเซลล์ว่างเหมือนกันหมด, จำนวนเต็ม 20 หลักสูญเสียความแม่นยำตั้งแต่ 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]
# อีเมลของเรกคอร์ดที่สองหายไปโดยไม่มี error หรือคำเตือน
คีย์ 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); ตัวแปลง Excel ของ toolvore เดินทางนี้ มันมีคลาสความกำกวมเดียวกัน — คีย์ที่มี/ต้องเขียน~1— แต่กฎ escape ถูกมาตรฐานไว้ใน 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):
1. null, การขาดหาย และสตริงว่างกลายเป็นเซลล์ว่างเหมือนกันหมด ใส่ [{"a":null},{"b":""},{"c":1}] — สามเซลล์ออกมาว่างหมด CSV ไม่มีสามสถานะ มีแค่ "มีอักขระ" กับ "ไม่มีอักขระ" หลังไปกลับสามอันอ่านได้เป็น "" หมด; ความหมายเดิมเรียกคืนไม่ได้
2. อ็อบเจกต์และอาร์เรย์ซ้อนกลายเป็นสตริง JSON เครื่องหมายคำพูดถูกคูณสองตาม RFC 4180 ({""name"":""张三""}) และเมื่อเอาเซลล์ไปวางใน JSON parser ใด ๆ ค่ากลับมา — เพราะฉะนั้น JSON ในเซลล์รักษาคงสภาพ
3. จำนวนเต็ม 20 หลักสูญเสียความแม่นยำตั้งแต่การแยกวิเคราะห์ — ความผิดไม่ใช่ของ CSV ใส่ "id": 9007199254740993; ใน CSV เป็น 9007199254740992 ไปแล้ว JSON.parse ใน JavaScript ใช้ IEEE 754 double: ความสูญเสียเกิดตอนแยกวิเคราะห์ ก่อนตัวแปลง CSV ใด ๆ Python json.load เก็บไว้ครบ ฟิลด์ประเภทตัวระบุ (snowflake ID เลขคำสั่งซื้อ) ควรมาเป็นสตริงตั้งแต่ฝั่งผู้ผลิต อ่านเพิ่มเรื่องความแม่นยำของตัวเลขได้ที่ ทำไมตัวเลขใหญ่สูญเสียความแม่นยำ
4. เลขศูนย์นำหน้าในสตริงปลอดภัยที่ชั้นข้อความของ CSV "phone": "00123" ยังเป็น 00123 ใน CSV เพราะมันเป็นสตริงตั้งแต่ต้นจนจบและไม่มีอะไรข่วนมัน ข่วนศูนย์คือขั้นถัดมาที่ Excel ถือเซลล์เป็นตัวเลข — ดู สี่วิธีแปลง JSON เป็น Excel
5. ฟิลด์ที่มีคอมมา เครื่องหมายคำพูด หรือขึ้นบรรทัดใหม่ papaparse ทำตาม RFC 4180: ครอบฟิลด์ด้วยเครื่องหมายคำพูด คูณสองเครื่องหมายด้านใน "He said \"hi\", ok" ออกมาเป็น "He said ""hi"", ok" และผ่านไปกลับได้ไม่บาดเจ็บ ซีเรียลไลเซอร์เขียนมือที่ลัดไลบรารีแทบตายตรงนี้เสมอ
ไปกลับ: สวิตช์อนุมานเปลี่ยนอะไร
CSV → JSON ต้องเดาชนิดของทุกเซลล์ ตัวแปลงของเรา ปิดการอนุมานชนิดเป็นค่าเริ่มต้น และคืนทุกอย่างเป็นสตริง:
[{ "id": "9007199254740992", "phone": "00123" }]
เปิด typeInference แล้ว id กลายเป็นตัวเลข (ความแม่นยำหายไปแล้ว) ส่วน phone ยังเป็นสตริง — กฎอนุมานตัดรูปแบบมีศูนย์นำหน้าอย่าง 010 ออก เพราะไม่ใช่ number literal ที่ถูกต้อง สตริงรอดด้วยดวง
จดความไม่สมมาตรนี้ไว้: อนุมานปิดแย่ที่สุดก็เสียชนิด; อนุมานเปิดอาจเสียข้อมูล คอลัมน์ที่ต้องการตัวเลขควรแปลงด้วยนิยามคอลัมน์แบบชัดเจน ไม่ฝากไว้กับการอนุมานทั้งระบบ การนำไปปฏิบัติ rowsToRecords ในบทความก่อนหน้าของเรา JSON, CSV และข้อมูลตาราง คือหลักการนี้ในรูปโค้ด
เลือกกลยุทธ์ตามปลายทาง: ตารางตัดสินใจ
| ใครรับ CSV | กลยุทธ์หัวคอลัมน์ | จัดการอาร์เรย์อย่างไร | ชนิดข้อมูล |
|---|---|---|---|
| เพื่อนร่วมงานที่เปิดครั้งเดียว | ยูเนียนคีย์ | JSON ในเซลล์ | ไม่อนุมาน |
| Excel / รายงาน | ยูเนียนคีย์ | JSON ในเซลล์ หรือคอลัมน์มีดัชนีถ้าความยาวคงที่ | คอลัมน์ตัวระบุบังคับเป็นข้อความ |
| นำเข้าฐานข้อมูล | รายการคอลัมน์แบบชัดเจน | ตาราง master/detail แยกกัน หรือระเบิด | นิยามคอลัมน์แบบชัดเจน |
| BI / การรวม | รายการคอลัมน์แบบชัดเจน | แถวระเบิด ความละเอียด = หน่วยวิเคราะห์เล็กที่สุด | นิยามคอลัมน์แบบชัดเจน |
| ไปป์ไลน์อัตโนมัติ | รายการคอลัมน์แบบชัดเจน | JSON ในเซลล์ ระเบิดที่ปลายทาง | อนุมานปิด แปลงทีละคอลัมน์ |
กฎร่วม: ยิ่งใกล้คน ยิ่งคลายได้; ยิ่งใกล้เครื่อง ยิ่งต้องชัดเจนทุกอย่าง
เช็กลิสต์สำหรับคัดลอก
- [ ] หัวคอลัมน์มาจากยูเนียนคีย์ครบหรือรายการชัดเจน? ถ้าไม่ พิสูจน์ได้ไหมว่าไม่มีเรกคอร์ดไหนหายฟิลด์?
- [ ] คีย์มีจุดหรือสแลชได้ไหม? ตรวจการชนกับตัวคั่นการแผ่แล้ว?
- [ ] กลยุทธ์อาร์เรย์มีเอกสารหรือยัง? พฤติกรรมอาร์เรย์ว่างตัดสินใจแล้ว?
- [ ] สำหรับ
null, การขาดหาย และสตริงว่าง: ปลายทางอ่านได้อะไรจากแต่ละอัน? เลือกกฎหนึ่งข้อแล้วตรึงมัน - [ ] คอลัมน์ประเภทตัวระบุ (ID เลขคำสั่งซื้อ โทรศัพท์) เดินทางเป็นสตริงตั้งแต่ต้นจนจบ?
- [ ] หลังระเบิด จำนวนแถวเท่ากับผลรวมความยาวอาร์เรย์? ตรวจแบบสุ่มแล้ว?
- [ ] ฟิลด์ตัวอย่างที่มีคอมมา เครื่องหมายคำพูด และการขึ้นบรรทัดใหม่ ผ่านไปกลับได้ครบถ้วน?
- [ ] การอนุมานชนิดทั้งระบบปิดตอนนำเข้าซ้ำ พร้อมนิยามชัดเจนสำหรับคอลัมน์ตัวเลข?
เมื่อทั้งแปดมีคำตอบชัดเจนเท่านั้น CSV นี้จึง "ถูกต้อง" — ไม่ใช่แค่ "แปลงสำเร็จ"
FAQ
ทำไมตัวแปลงไม่แผ่อ็อบเจกต์ซ้อนเป็นคอลัมน์เป็นค่าเริ่มต้น?
การแผ่ทำให้จำนวนคอลัมน์หลุดมือ: การซ้อนลึกผลิตชื่ออย่าง user.address.geo.lat และรวมกับยูเนียนคีย์ เรกคอร์ดรูปทรงต่างเพียงตัวเดียวก็เพิ่มคอลัมน์เป็นกอง สตริง JSON ในเซลล์รักษาคงสภาพและจำนวนคอลัมน์คงที่; แผ่แบบชัดเจนเมื่อต้องการ
หัวคอลัมน์มีชื่อซ้ำกันต้องทำอย่างไร?
การชนของสองคีย์ต่างกันหลังแผ่ (อย่าง a.b เทียบ a→b แบบซ้อน) แก้ที่ต้นน้ำ: เปลี่ยนตัวคั่นหรือไปใช้ JSON Pointer ส่วนซ้ำในอ็อบเจกต์เดียวกันตัวแปลงมักเก็บค่าหลังสุด; ซ้ำในหัวคอลัมน์ขึ้นกับการนำไปปฏิบัติ — unparse ของ papaparse ไม่ลบซ้ำ และ Excel ตอนเปิดจะรวมหรือทิ้งซ้ำเงียบ ๆ นี่คือเหตุผลที่มันอยู่ในเช็กลิสต์
ฟิลด์พ่อที่ซ้ำหลังระเบิดเป็นบั๊กหรือ?
ไม่ใช่ — นั่นคือดีไซน์ แถวระเบิดคือแถวรายละเอียด และแถวรายละเอียดต้องต่อกลับไปยังเรกคอร์ดพ่อด้วยคีย์ได้ รวมที่ปลายทางตามคีย์พ่อ; ถ้าขนาดน่ากังวล ส่งออกไฟล์รายละเอียดกับตาราง master แยกกัน
ตรวจยังไงว่าการแปลงของฉันไม่หายฟิลด์เลย?
ทำการตรวจสอบคีย์: คำนวณยูเนียนของคีย์ทั้งหมดใน JSON ต้นทาง แล้วเซตของหัวคอลัมน์ CSV แล้วหาผลต่าง ผลต่างที่ไม่ว่างแปลว่าฟิลด์หาย ในไปป์ไลน์แบบแบตช์เขียนสิ่งนี้เป็น assert ในเทสต์: การเปลี่ยนตัวแปลงใด ๆ ที่เริ่มหายฟิลด์จะล้มเหลวอัตโนมัติ