φυβλαςのβλογ
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
モンゴル語
言語学
maya
確率論
日本での日記
中国での日記
-- 北京での日記
-- 香港での日記
-- 澳門での日記
台灣での日記
北欧での日記
他の国での日記
qiita
その他の記事

記事の類別



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

  記事を検索

  おすすめの記事

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

ไทย

日本語

中文