φυβλαςのβλογ
บล็อกของ phyblas



การอ่านเขียนไฟล์ json ใน python
เขียนเมื่อ 2019/04/27 22:03
แก้ไขล่าสุด 2024/02/24 19:35
json เป็นรูปแบบการเก็บข้อมูลที่นิยมใช้ใน javascript มีลักษณะคล้ายคลึงกับดิกชันนารีและลิสต์ในภาษาไพธอน

ในไพธอนมีมอดูลชื่อ json เอาไว้สำหรับอ่านเขียนไฟล์ชนิด json โดยเฉพาะ

มอดูลนี้ใช้ไม่ยาก หลักๆก็มีอยู่ ๔ คำสั่งคือ dump, dumps, load, loads

json.dump(obj,fp) เขียน obj เป็นข้อความ json ลงไฟล์ fp
s = json.dumps(obj) แปลง obj เป็นข้อความ json
obj = json.load(fp) อ่านไฟล์ fp เป็น json
obj = json.loads(s) แปลงข้อความ json เป็นออบเจ็กต์

ในที่นี้ fp คือตัวไฟล์ json ที่เปิดขึ้นมาด้วยคำสั่ง open
ส่วน s เป็นสายอักขระที่เก็บข้อความ json ไว้
ส่วน obj คือออบเจ็กต์ของไพธอนที่ต้องการแปลงหรือแปลงมาจาก json

โดยรวมแล้วคือที่มี s ต่อท้ายจะเอาไว้แปลงระหว่างออบเจ็กต์กับสายอักขระ แต่ที่ไม่มี s จะไว้แปลงระหว่างออบเจ็กต์กับไฟล์

load ตรงข้ามกับ dump โดย load เป็นการแปลงจาก json เป็นออบเจ็กต์ในไพธอน ส่วน dump จะแปลงออบเจ็กต์เป็น json



การแปลงออบเจ็กต์เป็น json

ถ้ามีดิกชันนารีของลิสต์และทูเพิลอยู่ ถ้าใช้คำสั่ง dumps ก็จะได้เป็นสายอักขระ
import json
ab = {'a':[0,1],"b":(2,3)}
print(ab) # ได้ {'a': [0, 1], 'b': (2, 3)}
print(json.dumps(ab)) # ได้ {"a": [0, 1], "b": [2, 3]}

แต่ถ้าต้องการจะเขียนลงในไฟล์เลยก็ใช้ dump
with open('ab.json','w') as f:
    json.dump(ab,f)

แบบนี้ก็จะได้ไฟล์ ab.json เป็นไฟล์ json ที่เก็บข้อมูลออบเจ็กต์นั้นบันทึกลงในเครื่อง

จะเห็นว่าข้อความ json ที่ได้นั้นเป็นลักษณะโครงสร้างที่มีลักษณะคล้ายในไพธอน พอแปลงไปแล้วจึงไม่ได้เห็นว่าจะมีอะไรต่างไปจากเดิมนัก แต่ก็มีความแตกต่างให้เห็นได้บางส่วน

เช่น json จะใช้เครื่องหมายคำพูดคร่อมสายอักขระเป็น " " แต่ไพธอนจะใช้ ' ' เป็นหลัก ถึงเวลาเขียนจริงๆจะใช้ ' ' หรือ " " ก็ได้เหมือนกัน แต่เวลาแสดงผลในไพธอนโดยมาตรฐานแล้วจะใช้ ' '

และ json ไม่มีการแยกลิสต์กับทูเพิลเหมือนอย่างในไพธอน ดังนั้นจะถูกแปลงเป็นลิสต์ (ใน json เรียกว่า อาเรย์)

ออบเจ็กต์ชนิดต่างๆในไพธอนจะถูกแปลงเป็น json ในลักษณะดังนี้

dict > object
list,tuple > array
str > string
int,float > number
True > true
False > false
None > null

เพียงแต่ว่าในส่วนของคีย์ของดิกชันนารีนั้นใน json จะเป็นสายอักขระเสมอ ดังนั้นแม้จะใส่เป็นตัวเลขไปก็จะถูกแปลงเป็นชนิดสายอักขระ (คือมีเครื่องหมายคำพูดคร่อม)

ตัวอย่าง
ab = [{1:2.3,'a':('b','c')},None,False,True]
print(json.dumps(ab)) # ได้ [{"1": 2.3, "a": ["b", "c"]}, null, false, true]

ถ้าหากใส่ออบเจ็กต์ชนิดนอกเหนือจากที่กำหนดมานี้ลงไปก็จะแปลงไม่ได้

เช่น เซ็ต
print(json.dumps({2,1})) # ได้ TypeError: Object of type set is not JSON serializable

สามารถแก้ได้โดยเติมตัวเลือกเสริม default ลงไป เพื่อบอกว่าจะใช้ฟังก์ชันอะไรมาจัดการกับตัวแปรที่ไม่สามารถแปลงได้ปกติ

เช่นถ้าต้องการให้เมื่อเจอเซ็ตก็จะถูกเปลี่ยนเป็นลิสต์หรือสายอักขระหรือเป็นอะไรบางอย่างที่จะเลี่ยงข้อผิดพลาดได้ก่อนที่จะแปลงเป็น json อีกที
print(json.dumps([{1,2},2],default=list)) # ได้ [[1, 2], 2]
print(json.dumps([{1,2},2],default=str)) # ได้ ["{1, 2}", 2]
print(json.dumps([{1,2},2],default=sum)) # ได้ [3, 2]
print(json.dumps([{1,2},2],default=lambda x: '@'.join([str(s) for s in x]))) # ได้ ["1@2", 2]

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

ถ้าใช้คีย์เป็นทูเพิลแล้วพอแปลงเป็น json จะเกิดข้อผิดพลาดขึ้น เช่น
print(json.dumps({(1,2):1})) # ได้ TypeError: keys must be str, int, float, bool or None, not tuple

เพียงแต่ว่าหากใส่ตัวเลือกเสริม skipkeys=True ลงไปจะไม่เกิดข้อผิดพลาด แต่คีย์เวิร์ดที่เป็นทูเพิลจะถูกข้ามไป เช่น
print(json.dumps({(1,2):1,3:4},skipkeys=True)) # ได้ {"3": 4}

นอกจากนี้ ในไพธอนมีจำนวนที่เป็น nan, inf หรือ -inf พวกนี้ถ้าใส่ใน json จะกลายเป็น NaN, Infinity, -Infinity
a = float('-inf')
b = float('nan')
c = float('inf')
print([a,b,c]) # ได้ [-inf, nan, inf]
print(json.dumps([a,b,c])) # ได้ [-Infinity, NaN, Infinity]

แต่ถ้าหากใส่ตัวเลือก allow_nan=False ลงไปเมื่อมีจำนวนพวกนี้จะเกิดข้อผิดพลาดขึ้นมา
print(json.dumps([a,b,c],allow_nan=False)) # ได้ ValueError: Out of range float values are not JSON compliant

ดิกชันนารีในไพธอนก่อนเวอร์ชัน 3.6 จะไม่สนลำดับของข้อมูลที่ใส่เข้าไป ดังนั้นเวลาที่แปลงเป็น json จะได้ลำดับแบบไหนก็อาจคาดเดายาก
dic = {}
dic['d'] = 1
dic['i'] = 2
dic['c'] = 3
print(dic) # ได้ {'i': 2, 'c': 3, 'd': 1}
print(json.dumps(dic)) # ได้ {"i": 2, "c": 3, "d": 1}

เพื่อที่จะให้เรียงลำดับตามที่ต้องการอาจต้องใช้ OrderedDict แทน dict ธรรมดา
from collections import OrderedDict
dic = OrderedDict()
dic['d'] = 1
dic['i'] = 2
dic['c'] = 3
print(dic) # ได้ OrderedDict([('d', 1), ('i', 2), ('c', 3)])
print(json.dumps(dic)) # ได้ {"d": 1, "i": 2, "c": 3}

เพียงแต่ว่าในไพธอนตั้งแต่ 3.6 เป็นต้นไปดิกชันนารีจะมีการจำลำดับ จึงไม่จำเป็นต้องใช้ OrderedDict แล้ว

หากต้องการให้มีการจัดลำดับคีย์ใหม่ให้เรียงกันเป็นระเบียบก็สามารถใส่ตัวเลือกเสริม sort_keys=True
dic = {'d':1,'i':2,'c':3}
print(json.dumps(dic,sort_keys=True)) # ได้ {"c": 3, "d": 1, "i": 2}

หากต้องการให้ข้อความ json ที่ได้นั้นแบ่งเป็นหลายบรรทัดแทนที่จะเป็นบรรทัดเดียวต่อกันยาวก็ให้เติมตัวเลือกเสริม indent ลงไป โดยใส่จำนวนวรรคในการร่น

เช่น
print(json.dumps({1:2,3:[4,[5,{6:7,8:9}]]},indent=4))
ได้
{
    "1": 2,
    "3": [
        4,
        [
            5,
            {
                "6": 7,
                "8": 9
            }
        ]
    ]
}


ข้อมูลในดิกชันนารีปกติจะกั้นด้วย : ส่วนในลิสต์จะกั้นด้วย , โดยมีเว้นวรรคตามหนึ่งช่อง แต่หากอยากเปลี่ยนให้วรรคมากขึ้นหรือน้อยลง หรือเปลี่ยนเป็นตัวอื่นก็กำหนดได้โดยตั้งที่ตัวเลือกเสริม separators

ตัวอย่าง
print(json.dumps({1:2,3:[4,[5,{6:7,8:9}]]}))
# ได้ {"1": 2, "3": [4, [5, {"6": 7, "8": 9}]]}
print(json.dumps({1:2,3:[4,[5,{6:7,8:9}]]},separators=[',',':']))
# ได้ {"1":2,"3":[4,[5,{"6":7,"8":9}]]}
print(json.dumps({1:2,3:[4,[5,{6:7,8:9}]]},separators=[',  ',':  ']))
# ได้ {"1":  2,  "3":  [4,  [5,  {"6":  7,  "8":  9}]]}
print(json.dumps({1:2,3:[4,[5,{6:7,8:9}]]},separators=['+ ','| ']))
# ได้ {"1"| 2+ "3"| [4+ [5+ {"6"| 7+ "8"| 9}]]}


ปัญหาอีกอย่างของคำสั่ง json คือหากเจออักษรที่ไม่ใช่ ascii จะถูกแปลงเป็นรหัส สามารถแก้ปัญหานี้ได้โดยใส่ตัวเลือกเสริม ensure_ascii=False
print(json.dumps(['ก','๑'])) # ได้ ["\u0e01", "\u0e51"]
print(json.dumps(['ก','๑'], ensure_ascii=False)) # ได้ ["ก", "๑"]

สรุปตัวเลือกทั้งหมดในการแปลงออบเจ็กต์ไปเป็น json ด้วยคำสั่ง dump หรือ dumps

ตัวเลือกเสริม คำอธิบาย ค่าตั้งต้น
skipkeys เมื่อเจอคีย์ที่เป็นทูเพิลถ้าเป็น True จะข้าม ถ้าเป็น False จะขึ้น TypeError False
ensure_ascii ถ้าเป็น True จะแปลงอักษรที่ไม่ใช่ ascii ให้เป็นรหัส True
check_circular ถ้า True จะป้องกันการวนซ้ำไม่รู้จบที่อาจเกิดขึ้นขณะแปลง เช่น OverflowError True
allow_nan เมื่อเจอ nan, inf, inf ถ้าเป็น True จะถูกแปลงเป็น NaN, Infinity, -Infinity ถ้า False จะขึ้น ValueError True
indent ถ้าใส่ตัวเลขจะจัดแบ่งข้อมูลเป็นบรรทัดโดยจำนวนเคาะวรรคที่จะร่นตามที่ใส่ ถ้าใส่ None จะรวมกันอยู่บรรทัดเดียว None
separators ตัวที่ใช้ในการคั่นลิสต์และดิกชันนารี [', ', ': ']
default ฟังก์ชันที่จะจัดการเมื่อเจอตัวแปรที่โดยมาตรฐานแล้วไม่สามารถแปลงเป็น json ได้ None
sort_keys จัดเรียงข้อมูลในดิกชันนารีตามคีย์ False



การอ่านไฟล์ json

หากมีไฟล์ json อยู่แล้วต้องการอ่านขึ้นมาเป็นออบเจ็กต์ในไพธอนก็ใช้คำสั่ง load
with open('obj.json') as f:
    obj = json.load(f)

แต่ถ้ามีข้อความ json เก็บไว้ในตัวแปรอยู่แล้วหรือจะเขียนข้อความ json ลงไปโดยตรงก็ใช้คำสั่ง loads แปลงเป็นออบเจ็กต์
obj = json.loads('["a","b"]')
print(obj) # ได้ ['a', 'b']

เมื่อทกำการแปลง ข้อมูลจาก json จะถูกแปลงเป็นออบเจ็กต์ในไพธอนดังนี้

object > dict
array > list
string > str
number (int) > int
number (real) > float
true > True
false > False
null > None

ตัวอย่าง
print(json.loads('["ก",1,1.2,null,false,true,{"3":5}]')) # ได้ ['ก', 1, 1.2, None, False, True, {'3': 5}]

หากมีข้อมูลที่เป็นรหัส ซึ่งอาจเกิดจากการที่ตอนใช้คำสั่ง dumps ไม่ได้ใส่ ensure_ascii=False เวลาใช้ loads ก็จะถูกแปลงกลับเป็นตัวอักษรด้วย
print(json.dumps(['ฒ'])) # ได้ ["\u0e12"]
print(json.loads('["\u0e12"]')) # ได้ ['ฒ']

ปกติแล้วตัวเลขจำนวนเต็มจะถูกตีความแปลงเป็น int แต่สามารถใส่ตัวเลือกเสริม parse_int ลงไปเพื่อให้มีการจัดการเป็นอย่างอื่นได้

เช่นสามารถทำให้เลขจำนวนเต็มทั้งหมดเป็นเลขทศนิยมได้
print(json.loads('[1,2.1]',parse_int=float)) # ได้ [1.0, 2.1]

ในทำนองเดียวกัน ตัวเลือกเสริม parse_float เอาไว้จัดการกับ float

เช่นหากต้องการทำให้เลขทศนิยมทั้งหมดกลายเป็นจำนวนเต็ม
print(json.loads('[1,2.1]',parse_float=lambda x:int(float(x)))) # ได้ [1, 2]

ปกติ NaN, Infinity, -Infinity ก็จะถูกแปลงเป็น nan, inf, -inf
print(json.loads('[-Infinity,NaN,Infinity]')) # ได้ [-inf, nan, inf]

แต่ถ้าต้องการให้ถูกตีความต่างออกไปสามารถกำหนดตัวเลือกเสริม parse_constant ได้ เช่น
print(json.loads('[-Infinity,NaN,Infinity]',parse_constant=str)) # ได้ ['-Infinity', 'NaN', 'Infinity']

ตัวเลือกเสริม object_hook มีไว้กำหนดว่าหากเจอข้อมูลที่เป็นดิกชันนารีจะให้ใช้ฟังก์ชันอะไรจัดการเป็นพิเศษหรือเปล่า

นอกจากนี้ยังมีตัวเลือกเสริม object_pairs_hook จะคล้ายกับ object_hook แต่ต่างกันตรงที่จะเริ่มอ่านดิกชันนานีนั้นในรูปของลิสต์ของทูเพิล

object_hook กับ object_pairs_hook จะไม่ถูกใช้พร้อมกัน ถ้าใส่ทั้งคู่ object_pairs_hook จะถูกใช้ ส่วน object_hook จะถูกมองข้ามไป

ตัวอย่าง
ab = '[{"a":1,"b":2},3]'
print(json.loads(ab,object_hook=lambda x: tuple(x.items()))) # ได้ [(('a', 1), ('b', 2)), 3]
print(json.loads(ab,object_hook=str)) # ได้ ["{'a': 1, 'b': 2}", 3]
print(json.loads(ab,object_pairs_hook=str)) # ได้ ["[('a', 1), ('b', 2)]", 3]
print(json.loads(ab,object_pairs_hook=lambda x:['%s~%s'%s for s in x])) # ได้ [['a~1', 'b~2'], 3]



JSONDecoder และ JSONEncoder

เมื่อใช้ dumps หรือ loads แล้วต้องการใส่ตัวเลือกเสริมจะต้องใส่ทุกครั้งที่ต้องการแปลง แต่ถ้าหากต้องการให้เวลาแปลงทุกครั้งใช้ตัวเลือกเสริมนั้นตลอดโดยที่ไม่ต้องมาคอยใส่ทุกครั้งก็อาจใช้ JSONDecoder และ JSONEncoder ช่วยแทน

สร้างออบเจ็กต์ JSONEncoder ขึ้นมาโดยใส่ตัวเลิกเสริมลงไปตอนสร้าง จากนั้นใช้คำสั่ง encode เพื่อทำงานแทน dumps ได้
je = json.JSONEncoder(ensure_ascii=False)
print(je.encode(['ก','ข'])) # ได้ ["ก", "ข"]
print(je.encode(['あ','の'])) # ได้ ["あ", "の"]

ในทำนองเดียวกัน สร้างออบเจ็กต์ JSONDecoder ขึ้นมาแล้วใช้คำสั่ง decode เพื่อทำงานแทน loads ได้
jd = json.JSONDecoder(parse_int=float)
print(jd.decode('[1,2]')) # ได้ [1.0, 2.0]
print(jd.decode('[3,4,5]')) # ได้ [3.0, 4.0, 5.0]

หรือจะใช้วิธีสร้างคลาสย่อยขึ้นแล้วป้อนลงไปในตัวเลือกเสริม cls ตอนใช้ dumps หรือ loads ก็ได้
class Je(json.JSONEncoder):
    def __init__(self,**kw):
        super(Je,self).__init__(ensure_ascii=False)

class Jd(json.JSONDecoder):
    def __init__(self,**kw):
        super(Jd,self).__init__(parse_int=float)

print(json.dumps(['ฮ'],cls=Je)) # ได้ ["ฮ"]
print(json.loads('[0]',cls=Jd)) # ได้ [0.0]


อ้างอิง


-----------------------------------------

囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧

ดูสถิติของหน้านี้

หมวดหมู่

-- คอมพิวเตอร์ >> เขียนโปรแกรม >> python

ไม่อนุญาตให้นำเนื้อหาของบทความไปลงที่อื่นโดยไม่ได้ขออนุญาตโดยเด็ดขาด หากต้องการนำบางส่วนไปลงสามารถทำได้โดยต้องไม่ใช่การก๊อปแปะแต่ให้เปลี่ยนคำพูดเป็นของตัวเอง หรือไม่ก็เขียนในลักษณะการยกข้อความอ้างอิง และไม่ว่ากรณีไหนก็ตาม ต้องให้เครดิตพร้อมใส่ลิงก์ของทุกบทความที่มีการใช้เนื้อหาเสมอ

สารบัญ

รวมคำแปลวลีเด็ดจากญี่ปุ่น
มอดูลต่างๆ
-- numpy
-- matplotlib

-- pandas
-- manim
-- opencv
-- pyqt
-- pytorch
การเรียนรู้ของเครื่อง
-- โครงข่าย
     ประสาทเทียม
ภาษา javascript
ภาษา mongol
ภาษาศาสตร์
maya
ความน่าจะเป็น
บันทึกในญี่ปุ่น
บันทึกในจีน
-- บันทึกในปักกิ่ง
-- บันทึกในฮ่องกง
-- บันทึกในมาเก๊า
บันทึกในไต้หวัน
บันทึกในยุโรปเหนือ
บันทึกในประเทศอื่นๆ
qiita
บทความอื่นๆ

บทความแบ่งตามหมวด



ติดตามอัปเดตของบล็อกได้ที่แฟนเพจ

  ค้นหาบทความ

  บทความแนะนำ

ตัวอักษรกรีกและเปรียบเทียบการใช้งานในภาษากรีกโบราณและกรีกสมัยใหม่
ที่มาของอักษรไทยและความเกี่ยวพันกับอักษรอื่นๆในตระกูลอักษรพราหมี
การสร้างแบบจำลองสามมิติเป็นไฟล์ .obj วิธีการอย่างง่ายที่ไม่ว่าใครก็ลองทำได้ทันที
รวมรายชื่อนักร้องเพลงกวางตุ้ง
ภาษาจีนแบ่งเป็นสำเนียงอะไรบ้าง มีความแตกต่างกันมากแค่ไหน
ทำความเข้าใจระบอบประชาธิปไตยจากประวัติศาสตร์ความเป็นมา
เรียนรู้วิธีการใช้ regular expression (regex)
การใช้ unix shell เบื้องต้น ใน linux และ mac
g ในภาษาญี่ปุ่นออกเสียง "ก" หรือ "ง" กันแน่
ทำความรู้จักกับปัญญาประดิษฐ์และการเรียนรู้ของเครื่อง
ค้นพบระบบดาวเคราะห์ ๘ ดวง เบื้องหลังความสำเร็จคือปัญญาประดิษฐ์ (AI)
หอดูดาวโบราณปักกิ่ง ตอนที่ ๑: แท่นสังเกตการณ์และสวนดอกไม้
พิพิธภัณฑ์สถาปัตยกรรมโบราณปักกิ่ง
เที่ยวเมืองตานตง ล่องเรือในน่านน้ำเกาหลีเหนือ
ตระเวนเที่ยวตามรอยฉากของอนิเมะในญี่ปุ่น
เที่ยวชมหอดูดาวที่ฐานสังเกตการณ์ซิงหลง
ทำไมจึงไม่ควรเขียนวรรณยุกต์เวลาทับศัพท์ภาษาต่างประเทศ

บทความแต่ละเดือน

2024年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2023年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2022年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2021年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2020年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

ค้นบทความเก่ากว่านั้น

ไทย

日本語

中文