คู่มือ

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

อัปเดต: · Skillaria.Top

สำหรับ 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 คือทำงานกับไฟล์

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

PythonJSON
dictออบเจกต์ {}
list, tupleอาร์เรย์ []
strสตริง
int, floatตัวเลข
True / Falsetrue / false
Nonenull

ทูเพิลจะกลายเป็นลิสต์เมื่ออ่านกลับ ส่วนเซต วันที่ และ 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

คำถามที่พบบ่อย

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

เรียนหัวข้อนี้ผ่านวิดีโอ

หัวข้อนี้สอนอย่างละเอียดพร้อมฝึกปฏิบัติในคอร์ส “Python 3.15: от новичка до профи”

ไปที่คอร์ส