Python JSON 模組 - 走到跑,跑到飛的 orjson
Posted on May 10, 2023 in Python 模組/套件推薦 by Amo Chen ‐ 4 min read
orjson 是一套由 Rust 實作的 Python 套件,專門用以處理 JSON 相關的 encode 與 decode 的工作,效率不僅快(根據官方測試最快可以達到 json 模組的 40 到 50 倍效率)更天生能直接處理 Python 內建 json 模組所無法序列化(serialize)的 datetime, UUID, dataclass 等資料,不需額外編寫序列化的處理程式。
如果你想改善 Python API server 處理 JSON 的速度,以降低系統回應時間,又或者你有大量 JSON 相關的資料要處理,想有效減少處理時間的話,不妨試試 orjson 吧!
前言
Python 雖然有內建 json 模組供開發者處理 JSON 格式的資料,例如將資料輸出為 JSON 格式,或將 JSON 字串載入並轉為 Python 資料。
不過 Python 內建的 json 模組效率卻不夠好之外,使用上也容易因為遇到 dataclass, datetime, UUID 等無法序列化(serialize)的資料,而造成 TypeError
, 因此在輸出 JSON 格式的字串時,都需要針對 set, datetime 或 UUID 等型別的資料先轉換成 int, float 或 str 等型態,才能正確輸出 JSON 格式的字串,詳細問題與做法可以參考 製作 JSON serializable 的類別 一文。
也許小型的應用(application)感覺不出差異,一旦應用長大到每秒要處理相當多的 JSON 資料時,勢必要使用更快、更方便的 JSON 模組以加速應用處理 JSON 的速度。
本文所介紹的 orjson 就是解決上述執行效率與 JSON 序列化問題的最佳解決方案——快又方便有效。
本文環境
- Python 3
- Google Colab
- orjson
$ pip install orjson
內建 json 模組 vs. orjson
首先下列一段簡單的程式碼,就能讓內建的 json 模組出錯:
import json
from datetime import datetime
d = {
'time': datetime.now(),
}
j = json.dumps(d)
執行結果如下,可以看到內建 json 模組無法處理 datatime 型態的資料,因此拋出 TypeError: Object of type datetime is not JSON serializable
的例外錯誤(exception):
但是 orjson 天生就能夠處理 datetime, date 等型態的資料,因此不會發生問題,例如:
import orjson
from datetime import datetime
d = {
'time': datetime.now(),
}
j = orjson.dumps(d)
print(j)
上述執行結果如下,可以看到 orjson 能夠正常處理 datetime 型態的資料:
b'{"time":"2023-05-10T02:36:43.813289"}'
值得注意的是, orjson 所輸出的 JSON 格式是 bytes, 所以如果要轉為 Python string 需要呼叫 decode() 方法,例如:
orjson.dumps({'time': datetime.now()}).decode()
不過輸出 bytes 的好處是寫檔效率較好,可以將 open() 函式的 mode 參數設定為 binary mode, 例如 wb
直接將 bytes 寫入檔案(因為 Python 不需再額外呼叫 deocde 所以速度較快):
with open('output.jsonlines', 'wb') as file:
file.write(orjson.dumps(d, option=orjson.OPT_APPEND_NEWLINE)
如果不想輸出 datetime 的 microseconds, 可以設定 orjson 的 option 參數為 orjson.OPT_OMIT_MICROSECONDS
, 例如:
import orjson
from datetime import datetime
j = orjson.dumps(datetime.now(), option=orjson.OPT_OMIT_MICROSECONDS)
print(j)
上述執行結果如下,可以看到 orjson 只輸出到秒為止, microseconds 的部分被拿掉了:
b'"2023-05-10T05:51:31"'
orjson 也能正常處理含有時區(tzinfo)的 datetime 資料, 例如:
import orjson, datetime, zoneinfo
orjson.dumps(
datetime.datetime(
2023, 5, 1, 2, 3, 4, 5,
tzinfo=zoneinfo.ZoneInfo('Asia/Taipei'),
)
)
上述執行結果如下,可以看到結果中有 +08:00
時區的資訊:
b'"2022-12-01T02:03:04.000009+08:00"'
orjson 內建多種原本 Python json 模組無法處理的資料,例如:
此外,速度的部分我們也可以用 1 個簡單的 dict 型態的資料進行比較:
target = {
"message": "Hello, JSON",
"count": 10,
}
以下使用 %time
指令比較 json 與 orjson 的速度:
%time json.dumps(target)
%time orjson.dumps(target)
可以看到 orjson 至少快 json 模組至少 1 倍( 24 µs vs. 10 µs ):
不過這僅是我們所做的粗淺比較,詳細的 benchmark 可以參考 orjson 所提供的比較數據。
dataclass
另一個 orjson 吸引人的功能特點是 orjson 支援處理 dataclass 的 JSON 序列化(serialization),這也是內建 json 模組所無法處理的,例如下列範例:
import json
from dataclasses import dataclass
@dataclass
class User(object):
uid: int
name: str
role: str
user = User(1, "root", "admin")
json.dumps(user)
上述範例在試圖將 User dataclass 輸出為 JSON 時,出現 TypeError
的例外錯誤,如下圖:
不過 orjson 能夠正確處理 dataclass:
import orjson
from dataclasses import dataclass
@dataclass
class User(object):
uid: int
name: str
role: str
user = User(1, "root", "admin")
orjson.dumps(user)
上述執行結果如下,可以看到 User dataclass 已經被正確序列化:
numpy
numpy 的 JSON 序列化也是 orjson 所主打的功能特點,不過 orjson 預設並沒有開啟 numpy 序列化的功能,如下列範例會出現 TypeError
:
import orjson, numpy
orjson.dumps(
numpy.array([[1, 2, 3], [4, 5, 6]]),
)
如果要開啟 numpy 序列化功能,需要設定 orjson 的 option 參數 orjson.OPT_SERIALIZE_NUMPY
, 例如:
import orjson, numpy
orjson.dumps(
numpy.array([[1, 2, 3], [4, 5, 6]]),
option=orjson.OPT_SERIALIZE_NUMPY,
)
orjson.loads() 載入 JSON 資料
如果想載入 JSON 字串,可以呼叫 orjson.loads(), 該函式支援傳入 string 與 bytes:
傳入 string:
orjson.loads('{"uid":1,"name":"root","role":"admin"}')
傳入 bytes:
orjson.loads(b'{"uid":1,"name":"root","role":"admin"}')
因此如果是從檔案中讀取出來,也不用額外轉成字串處理,可以直接用 mode='rb'
讀檔案:
with open('input.jsonlines', 'rb') as file:
for line in file:
orjson.loads(line)
default 參數
雖然 orjosn 已經支援多種資料型態的序列化,但也是有無法序列化的資料型態,例如 decimal 就無法被序列化為 JSON, 因此導致 TypeError
發生:
import orjson, decimal
orjson.dumps(decimal.Decimal("0.123"))
這種情況下,可以設定 default 參數,如果是 orjson 無法序列化的資料,將會傳給 default 函式進行處理,如下列範例中的 default 函式,該函式會判斷傳進來的資料是否為 decimal, 如果是則轉為字串,若不是則拋出 TypeError
, 如果要處理更多不同的資料,擴充 default 函式即可:
import orjson, decimal
def default(obj):
if isinstance(obj, decimal.Decimal):
return str(obj)
raise TypeError
orjson.dumps(decimal.Decimal("0.123"), default=default)
更多相關做法可以參閱 製作 JSON serializable 的類別 一文。
總結
orjson 憑藉著 Rust 實作的效能優勢以及支援各種常見的資料型態序列化,不僅能夠減少 Python 開發者的負擔,也能有效提升 JSON 相關應用執行速度。
值得一提的是 orjson 並不提供 load()
與 dump()
2 個方法,如果你的 JSON 應用沒有用到 json.load() 與 json.dump() 2 個函式(少個 s ),只要使用以下 import 方式就可以無痛轉為使用 orjson:
import orjson as json
如果你在尋找一種方法能夠簡單無痛提升 JSON 相關應用的效能,不仿考慮使用 orjson 吧,肯定能夠帶來一些幫助!
References
GitHub - ijl/orjson: Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy