# JSON ใน Python: อ่านและเขียนไฟล์, CSV, pathlib และการเข้ารหัส UTF-8

URL: https://skillaria.top/th/guides/python/python-json

สำหรับ JSON ใน Python มีโมดูล json ในตัว โดย json.load อ่านข้อมูลจากไฟล์ที่เปิดแล้ว json.dump เขียนลงไฟล์ และ json.loads กับ json.dumps ทำงานเดียวกันกับสตริง เปิดไฟล์ด้วย with open(path, encoding="utf-8") สร้างพาธด้วย pathlib.Path และอ่านตารางด้วยโมดูล csv

การทำงานกับ JSON ใน Python ไม่ต้องใช้ไลบรารีภายนอก โมดูล `json` อยู่ในไลบรารีมาตรฐานและแปลงดิกชันนารีกับลิสต์เป็นข้อความและกลับกันได้ นอกจากนี้มักต้องใช้ร่วมกับสามเครื่องมืออื่น ได้แก่ `open` พร้อมคำสั่ง `with` โมดูล `pathlib` สำหรับพาธ และโมดูล `csv` สำหรับตาราง ด้านล่างเป็นทุกอย่างตามลำดับ พร้อมโค้ดที่รันได้จริงและข้อผิดพลาดที่พบบ่อย

## ฟังก์ชัน open ใน Python: โหมดและการเข้ารหัส

ไฟล์ข้อความเปิดด้วยฟังก์ชันในตัว `open(path, mode, encoding=...)` ซึ่งคืนออบเจกต์ไฟล์ที่มีเมธอด `read` `readline` และ `write` และวนลูปอ่านทีละบรรทัดได้

| โหมด | หน้าที่ |
| --- | --- |

| `"r"` | อ่าน (ค่าเริ่มต้น) หากไม่มีไฟล์จะเกิด `FileNotFoundError` |

| `"w"` | เขียน ไฟล์จะถูกสร้างหรือ**ล้างเนื้อหาทันทีที่เปิด** |

| `"a"` | เขียนต่อท้าย เนื้อหาเดิมไม่ถูกแตะต้อง |

| `"x"` | สร้างไฟล์ใหม่ หากมีอยู่แล้วจะเกิด `FileExistsError` |

| `"b"` | ต่อท้ายโหมดเพื่อทำงานกับไบต์ (`"rb"`, `"wb"`) |

### ทำไมต้องใช้ with

ไฟล์ต้องถูกปิด มิฉะนั้นข้อมูลอาจยังไม่ถูกเขียนลงดิสก์ และระบบยังถือ file descriptor ค้างไว้ คำสั่ง `with` ปิดไฟล์ให้อัตโนมัติ แม้เกิดข้อผิดพลาดขึ้นภายในบล็อกก็ตาม

```
with open("notes.txt", "w", encoding="utf-8") as f:
    f.write("บรรทัดแรก\n")
    f.write("บรรทัดที่สอง\n")

with open("notes.txt", encoding="utf-8") as f:
    for line in f:
        print(line.rstrip("\n"))
```

การวนลูปบนไฟล์จะอ่านทีละบรรทัด ไม่โหลดทั้งไฟล์เข้าหน่วยความจำ ดังนั้นประมวลผลไฟล์ล็อกขนาดกิกะไบต์ได้เช่นกัน เมธอด `f.read()` คืนเนื้อหาทั้งหมดเป็นสตริงเดียว ส่วน `f.readlines()` คืนลิสต์ของบรรทัด

### วิธีเขียนต่อท้ายไฟล์โดยไม่ลบของเดิม

ข้อผิดพลาดที่มือใหม่พบบ่อยที่สุดคือเปิดไฟล์ด้วยโหมด `"w"` เพื่อ "เพิ่มบรรทัด" โหมด `"w"` จะล้างไฟล์ตั้งแต่ตอนเปิด ก่อนจะมีการเรียก `write` ครั้งแรกด้วยซ้ำ หากต้องการเพิ่มข้อมูลท้ายไฟล์ให้ใช้ `"a"` และหากต้องการรับประกันว่าจะไม่เขียนทับไฟล์ที่มีอยู่แล้ว ให้ใช้ `"x"`

```
with open("log.txt", "a", encoding="utf-8") as f:
    f.write("บันทึกใหม่\n")
```

เมธอด `write` ไม่ได้ขึ้นบรรทัดใหม่ให้เอง ต้องเขียน `"\n"` ไว้ชัดเจน หรือจะใช้ `print(..., file=f)` ซึ่งจะขึ้นบรรทัดให้โดยอัตโนมัติก็ได้

## การเข้ารหัส UTF-8: ทำไมข้อความถึงเพี้ยน

ก่อน Python 3.15 หาก `open()` ไม่ระบุพารามิเตอร์ `encoding` จะใช้การเข้ารหัสของระบบ บน Linux และ macOS คือ UTF-8 แต่บน Windows ค่าเริ่มต้นมักเป็น cp1252 หรือ cp874 (ภาษาไทย) สคริปต์ที่ทำงานได้ดีบนเซิร์ฟเวอร์อาจแสดงข้อความเพี้ยนบนเครื่องของเพื่อนร่วมงาน หรือล้มด้วย `UnicodeDecodeError`

ใน Python 3.15 ตาม PEP 686 โหมด UTF-8 เปิดเป็นค่าเริ่มต้น `open()` ที่ไม่ระบุ `encoding` จะอ่านและเขียน UTF-8 บนทุกระบบปฏิบัติการ หากต้องการกลับไปพฤติกรรมเดิมใช้ตัวแปรสภาพแวดล้อม `PYTHONUTF8=0` หรือแฟล็ก `-X utf8=0` และหากต้องการขอการเข้ารหัสของระบบโดยตรงให้ใช้ `encoding="locale"`

```
import sys
print(sys.flags.utf8_mode)   # 1 ใน Python 3.15

open("legacy.txt", encoding="locale")     # การเข้ารหัสของระบบ
open("old_excel.csv", encoding="cp1251")  # ไฟล์ที่บันทึกด้วย cp1251
```

ด้านกลับของเรื่องนี้คือ ไฟล์ CSV เก่าจาก Excel หรือสคริปต์บน Windows ที่บันทึกด้วย cp1251 จะถูกอ่านเป็น UTF-8 และเกิด `UnicodeDecodeError` วิธีแก้คืออ่านด้วย `encoding="cp1251"` แล้วบันทึกใหม่เป็น UTF-8 หลักปฏิบัติไม่เปลี่ยน คือระบุ `encoding` ให้ชัดเจนเสมอ โค้ดจะทำงานเหมือนกันในทุกเวอร์ชัน

## pathlib ใน Python: พาธในรูปแบบออบเจกต์

การต่อพาธด้วยสตริง (`"data" + "/" + name`) ไม่สะดวกและทำงานผิดบนระบบปฏิบัติการต่างกัน โมดูล `pathlib` มีคลาส `Path` ซึ่งต่อพาธด้วยเครื่องหมาย `/` และมีคุณสมบัติที่อ่านเข้าใจได้

```
from pathlib import Path

data = Path("data")
csv_path = data / "expenses.csv"

print(csv_path.name)    # expenses.csv
print(csv_path.stem)    # expenses
print(csv_path.suffix)  # .csv
print(csv_path.parent)  # data
print(csv_path.exists())

data.mkdir(parents=True, exist_ok=True)
(data / "hello.txt").write_text("สวัสดี", encoding="utf-8")
print((data / "hello.txt").read_text(encoding="utf-8"))

for p in data.glob("*.csv"):
    print(p)
```

เมธอด `read_text` และ `write_text` ใช้แทนการเรียก `open` กับ `with` สำหรับไฟล์เล็ก ๆ ได้ อ็อบเจกต์ `Path` ส่งเข้า `open` `json` และ `csv` ได้ทั้งหมด หากต้องการพาธที่อ้างอิงจากตำแหน่งของสคริปต์ ไม่ใช่โฟลเดอร์ที่รันคำสั่ง ให้เขียน `Path(__file__).parent / "data"`

ใน Python 3.14 `Path` มีเมธอด `copy()` และ `move()` แล้ว ทำให้การคัดลอกไฟล์แบบง่ายไม่จำเป็นต้องใช้ `shutil` ในเวอร์ชันเดียวกันยังเพิ่มโมดูล `compression.zstd` สำหรับการบีบอัดแบบ Zstandard (PEP 784)

## JSON ใน Python: load, dump, loads, dumps

JSON คือรูปแบบข้อความสำหรับแลกเปลี่ยนข้อมูล API ส่งข้อมูลในรูปนี้ และไฟล์คอนฟิกหรือรายงานก็มักเก็บเป็น JSON โมดูล `json` ทำได้สองอย่างคือ การทำให้เป็นอนุกรม (serialize) จากออบเจกต์ Python เป็นข้อความ JSON และการถอดอนุกรม (deserialize) จากข้อความกลับเป็นออบเจกต์ มีฟังก์ชันสี่ตัว และความสับสนระหว่างตัวเหล่านี้เป็นคำถามที่พบบ่อย

| ฟังก์ชัน | หน้าที่ |
| --- | --- |

| `json.dumps(obj)` | ออบเจกต์ → สตริง JSON |

| `json.loads(s)` | สตริง JSON → ออบเจกต์ |

| `json.dump(obj, f)` | ออบเจกต์ → เขียนลงไฟล์ที่เปิดแล้ว |

| `json.load(f)` | อ่าน JSON จากไฟล์ที่เปิดแล้ว |

จำง่าย ๆ คือ ตัว `s` ท้ายชื่อย่อมาจาก string หมายถึงทำงานกับสตริง หากไม่มี `s` คือทำงานกับไฟล์

### การแมปชนิดข้อมูล

| Python | JSON |
| --- | --- |

| `dict` | ออบเจกต์ `{}` |

| `list`, `tuple` | อาร์เรย์ `[]` |

| `str` | สตริง |

| `int`, `float` | ตัวเลข |

| `True` / `False` | `true` / `false` |

| `None` | `null` |

ทูเพิลจะกลายเป็นลิสต์เมื่ออ่านกลับ ส่วนเซต วันที่ และ `Decimal` โมดูลนี้ไม่รู้จัก และจะแสดง `TypeError: Object of type ... is not JSON serializable`

### วิธีบันทึก JSON ลงไฟล์

```
import json
from pathlib import Path

report = {
    "month": "2026-10",
    "total": 15230.5,
    "categories": {"อาหาร": 8200, "เดินทาง": 2100},
    "tags": ["บ้าน", "งาน"],
}

path = Path("data/report.json")
path.parent.mkdir(parents=True, exist_ok=True)
with path.open("w", encoding="utf-8") as f:
    json.dump(report, f, ensure_ascii=False, indent=2)
```

พารามิเตอร์ `ensure_ascii=False` ทำให้ภาษาไทยถูกเก็บเป็นตัวอักษรตามจริง ถ้าไม่ใส่ ข้อความในไฟล์จะเป็นรหัสเช่น `"อ..."` ซึ่งเป็น JSON ที่ถูกต้อง แต่มนุษย์อ่านยาก `indent=2` จัดย่อหน้าให้อ่านง่าย

### วิธีอ่าน JSON จากไฟล์

```
with open("data/report.json", encoding="utf-8") as f:
    data = json.load(f)

print(data["total"])               # 15230.5
print(data["categories"]["อาหาร"])  # 8200
```

ผลลัพธ์เป็นดิกชันนารีหรือลิสต์ธรรมดา ใช้งานได้ตามปกติ หากไฟล์ไม่ใช่ JSON หรือถูกตัดกลางคัน จะเกิดข้อยกเว้น `json.JSONDecodeError` ที่บอกหมายเลขบรรทัดและตำแหน่ง ควรดักจับข้อยกเว้นนี้เมื่อข้อมูลมาจากภายนอก

### แยกวิเคราะห์สตริง JSON และแสดงผลให้อ่านง่าย

```
import json

raw = '{"name": "สมหญิง", "skills": ["python", "sql"], "active": true}'
user = json.loads(raw)
print(user["skills"][0])   # python

print(json.dumps(user, ensure_ascii=False, indent=2, sort_keys=True))
```

`sort_keys=True` เรียงคีย์ตอนแสดงผล ทำให้เปรียบเทียบไฟล์ใน git ได้ง่าย และจัดรูปแบบไฟล์จากเทอร์มินัลได้ด้วยยูทิลิตีในตัว: `python -m json.tool data/report.json`

คำตอบของ HTTP API ก็เป็นสตริง JSON เช่นกัน ไลบรารี `requests` แยกวิเคราะห์ด้วยเมธอด `response.json()` ซึ่งภายในก็คือ `json.loads` นั่นเอง

### ข้อควรระวัง: คีย์ของ JSON เป็นสตริงเสมอ

```
d = {1: "มกราคม", 2: "กุมภาพันธ์"}
back = json.loads(json.dumps(d))
print(back == d)   # False
print(back)        # {'1': 'มกราคม', '2': 'กุมภาพันธ์'}
# back[1] -> KeyError ต้องใช้ back["1"]

fixed = {int(k): v for k, v in back.items()}
```

ในออบเจกต์ JSON คีย์ต้องเป็นสตริงเท่านั้น ดังนั้นคีย์ที่เป็นตัวเลขจะกลายเป็นสตริงหลังผ่านการแปลงไปและกลับ ให้แปลงกลับเป็นตัวเลขตอนอ่านข้อมูล

### ชนิดข้อมูลของตัวเอง: วันที่และ Decimal

```
import json
from datetime import date

def default(obj):
    if isinstance(obj, date):
        return obj.isoformat()
    raise TypeError(f"ไม่รู้จักวิธีแปลง {type(obj).__name__} เป็น JSON")

print(json.dumps({"day": date(2026, 10, 1)}, default=default))
# {"day": "2026-10-01"}
```

ฟังก์ชันที่ส่งเข้าพารามิเตอร์ `default` จะถูกเรียกสำหรับทุกออบเจกต์ที่ไม่รู้จัก สำหรับกรณีซับซ้อนสามารถสืบทอดจาก `json.JSONEncoder` และเขียนเมธอด `default` ใหม่ได้

### วิธีเพิ่มสมาชิกลงใน JSON array

ไม่สามารถต่อท้าย JSON เหมือนบันทึกข้อความได้ ต้องอ่านไฟล์ แก้ไข แล้วเขียนทั้งไฟล์กลับ

```
path = Path("data/items.json")
items = json.loads(path.read_text(encoding="utf-8")) if path.exists() else []
items.append({"id": len(items) + 1, "title": "รายการใหม่"})
path.write_text(json.dumps(items, ensure_ascii=False, indent=2), encoding="utf-8")
```

หากมีข้อมูลจำนวนมากที่เพิ่มเข้ามาตลอดเวลา รูปแบบ JSON Lines จะสะดวกกว่า คือเก็บออบเจกต์ JSON หนึ่งตัวต่อหนึ่งบรรทัด และเปิดไฟล์ด้วยโหมด `"a"`

## CSV ใน Python: โมดูล csv

CSV คือตารางในไฟล์ข้อความ โดยแต่ละบรรทัดคือหนึ่งระเบียน และค่าคั่นด้วยเครื่องหมายจุลภาค โมดูล `csv` จัดการเครื่องหมายคำพูด จุลภาคที่อยู่ในค่า และการขึ้นบรรทัดใหม่ได้ถูกต้อง ซึ่ง `split(",")` แบบแมนนวลทำไม่ได้

```
import csv

with open("data/expenses.csv", newline="", encoding="utf-8") as f:
    reader = csv.DictReader(f)
    total = 0.0
    for row in reader:
        total += float(row["amount"])
        print(row["date"], row["category"], row["amount"])
print("รวม:", total)
```

`csv.DictReader` แปลงแต่ละบรรทัดเป็นดิกชันนารีโดยใช้หัวตารางของไฟล์เป็นคีย์ ค่าทั้งหมดเป็นสตริง ตัวเลขและวันที่ต้องแปลงเอง (`float`, `int`, `date.fromisoformat`) หากไฟล์ใช้ตัวคั่นเป็น `;` (ซึ่ง Excel บางภาษาบันทึกไว้เช่นนั้น) ให้ใส่พารามิเตอร์ `delimiter=";"`

```
rows = [
    {"date": "2026-10-01", "category": "อาหาร", "amount": 450},
    {"date": "2026-10-02", "category": "เดินทาง", "amount": 60},
]
with open("data/out.csv", "w", newline="", encoding="utf-8") as f:
    writer = csv.DictWriter(f, fieldnames=["date", "category", "amount"])
    writer.writeheader()
    writer.writerows(rows)
```

ตอนเขียนต้องใส่ `newline=""` ทุกครั้ง มิฉะนั้นบน Windows จะมีบรรทัดว่างแทรกระหว่างแถว หากไฟล์จะเปิดใน Excel ให้ใช้ `encoding="utf-8-sig"` เพราะ BOM ช่วยให้ Excel รู้จักว่าเป็น UTF-8

## จาก CSV ไปเป็น JSON: ตัวอย่างครบชุด

```
import csv
import json
from collections import defaultdict
from pathlib import Path

src = Path("data/expenses.csv")
dst = Path("data/report.json")

by_category = defaultdict(float)
with src.open(newline="", encoding="utf-8") as f:
    for row in csv.DictReader(f):
        by_category[row["category"]] += float(row["amount"])

report = {"total": sum(by_category.values()), "categories": dict(by_category)}
dst.write_text(json.dumps(report, ensure_ascii=False, indent=2), encoding="utf-8")
print(dst.read_text(encoding="utf-8"))
```

นี่คือโครงของสคริปต์รายงานทุกตัว คืออ่านข้อมูลจาก CSV รวมผลในดิกชันนารี แล้วเขียนออกเป็น JSON

## ข้อผิดพลาดที่พบบ่อย

- `FileNotFoundError` เกิดจากพาธที่สัมพันธ์กับโฟลเดอร์ที่รันคำสั่ง ไม่ใช่กับสคริปต์ ใช้ `Path(__file__).parent` แทน
- ไฟล์ถูกลบเนื้อหาหมด เพราะเปิดด้วย `"w"` แทน `"a"`
- `UnicodeDecodeError` เกิดจากไฟล์ที่ใช้การเข้ารหัสอื่น ให้ระบุ (`cp1251`, `utf-8-sig`) ให้ชัด
- `TypeError: Object of type set is not JSON serializable` ให้แปลงเป็นลิสต์ก่อน หรือส่งฟังก์ชันเข้า `default`
- `KeyError` หลังโหลด JSON เพราะคีย์ตัวเลขกลายเป็นสตริง
- ตั้งชื่อไฟล์ของตัวเองว่า `json.py` หรือ `csv.py` ทำให้มันบดบังโมดูลมาตรฐาน และ `import json` จะไปโหลดไฟล์นั้นแทน

## สรุป

สำหรับ JSON ใน Python ใช้ไลบรารีมาตรฐานก็เพียงพอ คือ `json.load` และ `json.dump` สำหรับไฟล์ `loads` และ `dumps` สำหรับสตริง พร้อม `ensure_ascii=False` และ `indent` เพื่อให้อ่านง่าย เปิดไฟล์ด้วย `with` และระบุการเข้ารหัสชัดเจน สร้างพาธด้วย `pathlib` และอ่านตารางด้วย `csv.DictReader` หัวข้อนี้ถูกสอนแบบลงมือทำในคอร์ส Python โดยโปรแกรมติดตามค่าใช้จ่ายอ่านรายการจาก CSV และบันทึกรายงานเป็น JSON

## FAQ

**json.load ต่างจาก json.loads อย่างไร?**

json.load อ่าน JSON จากออบเจกต์ไฟล์ที่เปิดแล้ว ส่วน json.loads แยกวิเคราะห์สตริง ในทำนองเดียวกัน json.dump เขียนลงไฟล์ ส่วน json.dumps คืนค่าเป็นสตริง

**จะบันทึกภาษาไทยใน JSON โดยไม่มีรหัส \u ได้อย่างไร?**

ส่ง ensure_ascii=False เข้าไปใน json.dump หรือ json.dumps และเปิดไฟล์ด้วย encoding="utf-8"

**ต้องติดตั้งไลบรารีเพื่อทำงานกับ JSON ใน Python หรือไม่?**

ไม่ต้อง โมดูล json อยู่ในไลบรารีมาตรฐาน เพียงเขียน import json ก็ใช้ได้

**จะเก็บวันที่หรือเซตไว้ใน JSON ได้หรือไม่?**

ไม่ได้โดยตรง ต้องแปลงก่อน เช่น วันที่เป็นสตริง ISO ด้วย isoformat และเซตเป็นลิสต์ หรือส่งฟังก์ชันเข้าพารามิเตอร์ default

**จะอ่าน JSON ขนาดใหญ่ที่ใหญ่กว่าหน่วยความจำได้อย่างไร?**

json.load อ่านไฟล์ทั้งไฟล์ จึงไม่เหมาะกับข้อมูลขนาดใหญ่ ให้เก็บข้อมูลในรูปแบบ JSON Lines แล้วแยกวิเคราะห์ทีละบรรทัดด้วย json.loads


---
Source: Skillaria.Top (https://skillaria.top)
