JSON เป็น Excel: สี่วิธีและจุดที่แต่ละวิธีสูญเสียความสมบูรณ์
สรุปสำคัญ: โครงสร้างซ้อน ฟิลด์ไม่สม่ำเสมอ และ array ของ array คือจุดที่การแปลง JSON เป็นสเปรดชีตพัง สี่วิธีเรียงตามเวลาที่ใช้จนได้ผล แต่ละวิธีมีข้อแลกเปลี่ยน

สรุปสั้น: array ของ object แบน ๆ หรือ object ที่ห่อ array แปลงเร็วที่สุดผ่านเครื่องมือออนไลน์; ใช้ Power Query เมื่อข้อมูลรีเฟรชทุกสัปดาห์ ใช้ pandas เมื่อมีฟิลด์ต้องทำความสะอาดเยอะ และใช้สคริปต์ Node เฉพาะเมื่อการแปลงต้องเข้าร่วมไปป์ไลน์ — แต่ต้องตัดสินว่า "หนึ่งแถว" หมายถึงอะไรก่อนแตะข้อมูลที่ซ้อนหรือคีย์ที่ไม่สม่ำเสมอ
การแปลงเองไม่ยาก สิ่งที่ยากคือการค้นพบทีหลังว่าตัวเลขกลายเป็นข้อความและการซ้อนหลายระดับยุบเหลือระดับเดียว อ่านโครงสร้างข้อมูลก่อนเลือกวิธีจะช่วยให้ไม่ต้องทำงานซ้ำ
1. ก่อนอื่น ดูว่าคุณมี JSON ชนิดไหน
เปิดไฟล์แล้วดูระดับนอกสุด:
| โครงสร้าง | รูปร่าง | ความยากในการแปลง |
|---|---|---|
| Array ของ object แบน | [{"a":1,"b":2},{...}] |
ง่ายที่สุด — หนึ่งแถวต่อหนึ่งเรกคอร์ด |
| Object ที่ห่อ array | {"data":[{...}],"total":100} |
ง่าย — เลือกระดับ data |
| Object ที่ซ้อน | {"user":{"name":"x"}} |
ปานกลาง — ตัดสินว่าจะแบนหรือเชื่อมเป็นสตริง |
| Array ของ array | {"tags":["a","b"]} |
ยากมาก — ใส่ในแถวเดียวไม่ได้ ต้องแยกตาราง |
| คีย์ไม่สม่ำเสมอ | แต่ละเรกคอร์ดมีฟิลด์ต่างกัน | ยากมาก — คอลัมน์จะว่างเป็นส่วนใหญ่ |
สองแบบแรกแปลงตรง ๆ ได้ด้วยอัตราความสำเร็จเกือบ 100%; สำหรับสามแบบหลัง ต้องตัดสินก่อนว่า "หนึ่งแถว" หมายถึงอะไร ไม่อย่างนั้นชีตที่ได้จะใช้งานไม่ได้
2. วิธีที่ 1: เครื่องมือออนไลน์ (1 นาที)
เปิด JSON เป็นตาราง วางข้อมูล → เลือกตัวคั่นและส่วนหัว → ส่งออก CSV → เปิดใน Excel เหมาะกับข้อมูลที่มีโครงสร้างดีตั้งแต่ไม่กี่สิบจนถึงไม่กี่พันแถว
สามสิ่งที่ต้องระวัง:
- คุณได้ CSV ไม่ใช่ xlsx เมื่อ Excel เปิด CSV มันเดาการเข้ารหัสจาก locale ของระบบ ข้อความภาษาจีนอาจกลายเป็นตัวขยะ การใช้ "Data → From Text/CSV" แล้วเลือก UTF-8 อย่างชัดเจนเชื่อถือได้กว่า
- ตัวระบุที่ขึ้นต้นด้วย
0สูญเสียศูนย์"007"เป็นข้อความใน CSV แต่ Excel แปลงเป็นตัวเลข 7 โดยอัตโนมัติ กำหนดคอลัมน์นั้นเป็นข้อความตอนนำเข้า หรือเติมคำนำหน้าให้ใน JSON - ตัวเลขยาวกลายเป็นสัญกรณ์วิทยาศาสตร์ บัตรประชาชนและเลขคำสั่งซื้อที่ยาวกว่า 15 หลักสูญเสียความแม่นยำ ฟิลด์แบบนี้ควรเป็นสตริงใน JSON อยู่แล้ว และต้องคงรูปแบบข้อความหลังนำเข้า
3. วิธีที่ 2: Power Query ที่มากับ Excel (ใช้ซ้ำได้)
เมื่อข้อมูลรีเฟรชทุกสัปดาห์ ให้ใช้ Power Query ที่มีใน Excel: ตั้งค่าครั้งเดียวแล้วคลิก "Refresh" ได้ตลอด:
- "Data" → "Get Data" → "From File" → "From JSON";
- หลังเลือกไฟล์คุณจะเข้าสู่ Power Query editor ซึ่งเห็นโครงสร้าง
List/Record; - คลิกไอคอนขยายทางด้านขวาของหัวคอลัมน์เพื่อขยายฟิลด์ที่ซ้อนทีละระดับ;
- เมื่อชื่อคอลัมน์และชนิดข้อมูลดูถูกต้องแล้ว "Close & Load"
ข้อดีคือการซ้อนขยายเป็นหลายคอลัมน์ได้ทีละชั้น และกำหนดชนิดข้อมูลด้วยมือได้; ราคาที่ต้องจ่ายคือการตั้งค่าครั้งแรกต้องเข้าใจความต่างระหว่าง List กับ Record — List คือ array, Record คือ object และขยายไปคนละทิศทาง
4. วิธีที่ 3: Python + pandas (ฟิลด์เยอะ ต้องทำความสะอาด)
import pandas as pd
# Object wrapping an array: first reach the list level
rows = pd.read_json('data.json')['data']
# Expand nested fields into multiple columns
frame = pd.json_normalize(rows, sep='_')
frame.to_excel('out.xlsx', index=False)
พารามิเตอร์สำคัญของ json_normalize คือ sep ซึ่งกำหนดรูปแบบการตั้งชื่อคอลัมน์หลังขยายฟิลด์ที่ซ้อน; user_name ใช้งานใน Excel ง่ายกว่า user.name array ของ array (เช่น tags) ต้องมีขั้นตอน explode เพิ่ม:
frame = frame.explode('tags')
ขั้นนี้แยกหนึ่งแถวเป็นหลายแถว เป็นทางเดียวที่จะรักษาความสัมพันธ์แบบหนึ่งต่อหลาย — โดยแลกกับการที่ฟิลด์ที่เหลือซ้ำกัน
5. วิธีที่ 4: สคริปต์ Node (เมื่อต้องเข้าร่วมไปป์ไลน์)
import { readFileSync, writeFileSync } from 'node:fs';
const rows = JSON.parse(readFileSync('data.json', 'utf8'));
const headers = [...new Set(rows.flatMap((row) => Object.keys(row)))];
const escapeCell = (value) => `"${String(value ?? '').replaceAll('"', '""')}"`;
const lines = [
headers.join(','),
...rows.map((row) => headers.map((key) => escapeCell(row[key])).join(',')),
];
writeFileSync('out.csv', '\uFEFF' + lines.join('\n'), 'utf8');
สองรายละเอียดที่ห้ามข้าม: เขียน \uFEFF (BOM) ที่ต้นไฟล์เพื่อให้ Excel รู้จัก UTF-8 และ escape เครื่องหมายคำพูดคู่ในเซลล์เป็น "" ไม่อย่างนั้นคอลัมน์จะเลื่อน
6. สามสิ่งที่การแปลงสูญเสียเสมอ
| สิ่งที่สูญเสีย | อาการ | วิธีแก้ |
|---|---|---|
| ข้อมูลชนิด | ตัวเลขและบูลีนกลายเป็นข้อความหรือ TRUE/FALSE |
กำหนดชนิดคอลัมน์ด้วยมือหลังนำเข้า หรือระบุใน Power Query |
| ระดับการซ้อน | ฟิลด์ลึกยุบเป็น [object Object] |
ใช้ json_normalize หรือขยายทีละระดับ — ห้ามใช้ String(obj) |
| ความสัมพันธ์หนึ่งต่อหลาย | array ถูกยัดลงเซลล์เดียว | แยกแถวด้วย explode หรือสร้างตารางที่เกี่ยวข้องแยก |
นอกจากนี้ null กับ "ฟิลด์ไม่มีอยู่" ดูเหมือนกันในชีต (ทั้งคู่เป็นเซลล์ว่าง) แต่ต่างความหมายในเชิงธุรกิจ เมื่อต้องแยกให้ชัด ให้แปลง null เป็นสตริง "null" อย่างชัดเจนใน JSON หรือเพิ่มคอลัมน์เครื่องหมาย _has_field
7. ตรวจสอบภายหลัง: อย่าดูแค่แถวแรก
หลังแปลงเสร็จ ให้สุ่มตรวจอย่างน้อยสามอย่าง:
- จำนวนเรกคอร์ด: จำนวนบรรทัดของ CSV ควรเท่ากับความยาวของ array + 1 (ส่วนหัว); บรรทัดที่หายมักหมายถึงการขึ้นบรรทัดใหม่ท้ายไฟล์ถูกกลืน;
- คอลัมน์ที่มีอัตรา null สูงสุด: เซลล์ว่างจำนวนมากบ่งชี้ว่าชื่อคีย์ไม่สม่ำเสมอ — กลับไปดูว่า JSON ต้นฉบับใช้สองการสะกด (
userIdกับuser_id) หรือไม่; - แถวที่ยาวที่สุด: array ที่ซ้อนมักตกที่นี่; ยืนยันว่าไม่มีอะไรถูกตัดทอน
เมื่อต้องทำย้อนกลับ (Excel/CSV กลับเป็น JSON) ให้ใช้ CSV เป็น JSON และจำไว้ว่าส่วนหัวถูกถือเป็นชื่อคีย์ — การเปลี่ยนส่วนหัวคือการเปลี่ยนชื่อฟิลด์
คำถามที่พบบ่อย
จะเลือกระหว่างเครื่องมือออนไลน์ Power Query และ pandas อย่างไร
ใช้เครื่องมือออนไลน์กับข้อมูลที่มีโครงสร้างดีตั้งแต่ไม่กี่สิบถึงไม่กี่พันแถวที่แปลงครั้งเดียว ใช้ Power Query เมื่อข้อมูลรีเฟรชทุกสัปดาห์ เพราะตั้งค่าครั้งเดียวและรีเฟรชได้ในคลิกเดียว ใช้ pandas กับ json_normalize เมื่อมีฟิลด์ต้องทำความสะอาดเยอะ และใช้สคริปต์ Node เมื่อการแปลงต้องเข้าร่วมไปป์ไลน์
ทำไมข้อความภาษาจีนถึงกลายเป็นตัวขยะใน CSV
การส่งออกเป็น CSV ไม่ใช่ xlsx และ Excel เดาการเข้ารหัสจาก locale ของระบบ UTF-8 ที่ไม่มี BOM จึงแสดงเป็นตัวขยะ ให้เขียน BOM \uFEFF ที่ต้นไฟล์ หรือเปิดผ่าน "Data → From Text/CSV" และเลือก UTF-8 อย่างชัดเจน
ควรแบน object ที่ซ้อนหรือแยกเป็นแถวเพิ่ม
ให้แบนทีละระดับแทนการวางเป็นสตริง: Power Query ขยายฟิลด์ที่ซ้อนเป็นคอลัมน์ และ json_normalize ที่มี sep ก็ทำเช่นเดียวกันใน pandas array ของ array ต้องมีขั้นตอน explode เพิ่มที่แยกหนึ่งแถวเป็นหลายแถว การส่ง object ลึกเข้า String(obj) ให้เพียง [object Object]