Python JSONエンコーダーが日時をサポートするようにするエレガントな方法はありますか?サードパーティのモジュールまたは簡単なハックですか?
私はtornadoのデータベースラッパーを使用してdbからいくつかの行をフェッチし、jsonを生成しています。クエリ結果には、通常のMySQLタイムスタンプ列が含まれます。
Pythonのデフォルトのjsonエンコーダーが、あらゆる種類のデータベースクエリで非常に一般的な独自の日時型をサポートしていないのは非常に厄介です。
Python独自のjsonエンコーダーを変更したくありません。良い習慣はありますか?どうもありがとう!
ps:Python JSONエンコーダーのデフォルトメソッドを変更して、ダーティハックを見つけました:
変化する:
def default(self, o):
raise TypeError(repr(o) + " is not JSON serializable")
に:
def default(self, o):
from datetime import date
from datetime import datetime
if isinstance(o, datetime):
return o.isoformat()
Elif isinstance(o, date):
return o.isoformat()
else:
raise TypeError(repr(o) + " is not JSON serializable")
まあ、それは開発環境のためだけの一時的な解決策になるでしょう。
しかし、長期的なソリューションまたは実稼働環境の場合、これは非常に醜いため、新しいサーバーにデプロイするたびに変更を行う必要があります。
もっと良い方法はありますか? Pythonコード自体、Tornadoソースコードも変更したくありません。これを実現するために自分のプロジェクトコードでできることはありますか?できれば1つのペースで。
どうもありがとう!
ドキュメントが示唆している JSONEncoderをサブクラス化し、独自のデフォルトメソッドを実装します。あなたは基本的にそこにいるようで、それは「ダーティハック」ではありません。
日付がデフォルトのエンコーダーで処理されない理由は、JSONに日付の標準表現がないためです。 一部の人 は/Date(1198908717056)/
形式を使用していますが、個人的にはISO形式を好みます。
import json
import datetime
class DateTimeEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, (datetime.datetime, datetime.date, datetime.time)):
return obj.isoformat()
Elif isinstance(obj, datetime.timedelta):
return (datetime.datetime.min + obj).time().isoformat()
return super(DateTimeEncoder, self).default(obj)
now = datetime.datetime.now()
encoder = DateTimeEncoder()
encoder.encode({"datetime": now, "date": now.date(), "time": now.time()})
> {"datetime": "2019-07-02T16:17:09.990126", "date": "2019-07-02", "time": "16:17:09.990126"}
json.dumps(thing, default=str)
私は自分のプロジェクトのために独自のクラスを作成しました。
import datetime
import decimal
import json
import sys
class EnhancedJSONEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, datetime.datetime):
ARGS = ('year', 'month', 'day', 'hour', 'minute',
'second', 'microsecond')
return {'__type__': 'datetime.datetime',
'args': [getattr(obj, a) for a in ARGS]}
Elif isinstance(obj, datetime.date):
ARGS = ('year', 'month', 'day')
return {'__type__': 'datetime.date',
'args': [getattr(obj, a) for a in ARGS]}
Elif isinstance(obj, datetime.time):
ARGS = ('hour', 'minute', 'second', 'microsecond')
return {'__type__': 'datetime.time',
'args': [getattr(obj, a) for a in ARGS]}
Elif isinstance(obj, datetime.timedelta):
ARGS = ('days', 'seconds', 'microseconds')
return {'__type__': 'datetime.timedelta',
'args': [getattr(obj, a) for a in ARGS]}
Elif isinstance(obj, decimal.Decimal):
return {'__type__': 'decimal.Decimal',
'args': [str(obj),]}
else:
return super().default(obj)
class EnhancedJSONDecoder(json.JSONDecoder):
def __init__(self, *args, **kwargs):
super().__init__(*args, object_hook=self.object_hook,
**kwargs)
def object_hook(self, d):
if '__type__' not in d:
return d
o = sys.modules[__name__]
for e in d['__type__'].split('.'):
o = getattr(o, e)
args, kwargs = d.get('args', ()), d.get('kwargs', {})
return o(*args, **kwargs)
if __name__ == '__main__':
j1 = json.dumps({'now': datetime.datetime.now(),
'val': decimal.Decimal('9.3456789098765434987654567')},
cls=EnhancedJSONEncoder)
print(j1)
o1 = json.loads(j1, cls=EnhancedJSONDecoder)
print(o1)
結果:
{"val": {"args": ["9.3456789098765434987654567"], "__type__": "decimal.Decimal"}, "now": {"args": [2014, 4, 29, 11, 44, 57, 971600], "__type__": "datetime.datetime"}}
{'val': Decimal('9.3456789098765434987654567'), 'now': datetime.datetime(2014, 4, 29, 11, 44, 57, 971600)}
参照:
注:タイプをキーと引数として、kwargsを値としてエンコーダーの__init__()
にカスタムディクショナリを渡し、それ(またはデフォルトのディクショナリ)をdefault()
メソッドで使用することで、より柔軟にできます。 。
json.dumps(r, default=lambda o: o.isoformat() if hasattr(o, 'isoformat') else o)
Trytonプロジェクトには、datetime.datetime
、datetime.date
、およびdatetime.time
オブジェクト(他のオブジェクトを含む)用のJSONEncoder実装があります。サーバーとクライアント間のJSONRPC通信に使用されます。
http://hg.tryton.org/2.4/trytond/file/ade5432ac476/trytond/protocols/jsonrpc.py#l5 を参照してください。
カスタムデコーダー/エンコーダーを作成します。
class CustomJSONEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, datetime.datetime):
return http_date(obj)
if isinstance(obj, uuid.UUID):
return str(obj)
return json.JSONEncoder.default(self, obj)
class CustomJSONDecoder(json.JSONDecoder):
def __init__(self, *args, **kwargs):
json.JSONDecoder.__init__(self, object_hook=self.object_hook, *args, **kwargs)
def object_hook(self, source):
for k, v in source.items():
if isinstance(v, str):
try:
source[k] = datetime.datetime.strptime(str(v), '%a, %d %b %Y %H:%M:%S %Z')
except:
pass
return source
日時タイプをUNIXタイムスタンプに変換してから、コンテンツをjsonにエンコードします。
カスタムエンコーダーを作成するだけです
(Coleの答えへの小さいが重要な追加は、pd.NaT(またはnull /空のタイムスタンプ値)の処理です。追加しないと、NaT /欠落したタイムスタンプデータの非常に奇妙なタイムスタンプ変換が発生するためです)
class CustomEncoder(json.JSONEncoder):
def default(self, obj):
if pd.isnull(obj):
return None
Elif isinstance(obj, datetime):
return obj.isoformat()
Elif isinstance(obj, date):
return obj.isoformat()
Elif isinstance(obj, timedelta):
return (datetime.min + obj).time().isoformat()
else:
return super(CustomEncoder, self).default(obj)
次に、それを使用してデータフレームをエンコードします。
df_as_dict = df.to_dict(outtype = 'records') # transform to dict
df_as_json = CustomEncoder().encode(df_as_dict) #transform to json
エンコーダーがデータを標準化したため、通常のデコーダーはデータをデータフレームに戻す際に正常に機能します。
result_as_dict = json.JSONDecoder().decode(df_as_json) # decode back to dict
result_df = pd.DataFrame(result) # transform dict back to dataframe
もちろん、これは、エンコードする前にデータフレームをより大きなdictに配置した場合にも機能します。
input_dict = {'key_1':val_1,'key_2':val_2,...,'df_as_dict':df_as_dict}
input_json = CustomEncoder().encode(input_dict)
input_json_back_as_dict = json.JSONDecoder().decode(input_json)
input_df_back_as_dict = input_json_back_as_dict['df_as_dict']
input_df_back_as_df = pd.DataFrame(input_df_back_as_dict)