29

Python JSONエンコーダーがdatetimeをサポートするエレガントな方法はありますか? サードパーティのモジュールまたは簡単なハック?

tornado のデータベース ラッパーを使用して、db からいくつかの行を取得し、json を生成しています。クエリ結果には、通常の MySQL タイムスタンプ カラムが含まれます。

Python のデフォルトの json エンコーダーが、あらゆる種類のデータベース クエリで非常に一般的な独自の datetime 型をサポートしていないのは非常に厄介です。

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 のソース コードも変更したくありません。これを実現するために自分のプロジェクト コードでできることはありますか? できればワンペースで。

どうもありがとう!

4

9 に答える 9

56

json.dumps(thing, default=str)

于 2014-10-04T17:38:04.870 に答える
27

ドキュメントでは、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"}
于 2012-08-26T01:21:10.670 に答える
21

プロジェクト用に独自のクラスを作成しました。

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()メソッドで使用することで、より柔軟にすることができます。

于 2014-04-29T09:54:09.563 に答える
6
json.dumps(r, default=lambda o: o.isoformat() if hasattr(o, 'isoformat') else o)
于 2016-10-14T14:05:30.437 に答える
1

Trytonプロジェクトにはdatetime.datetimedatetime.dateおよびdatetime.timeオブジェクト(他のオブジェクトを含む)用のJSONEncoder実装があります。サーバーとクライアント間のJSONRPC通信に使用されます。

http://hg.tryton.org/2.4/trytond/file/ade5432ac476/trytond/protocols/jsonrpc.py#l53を参照してください

于 2012-08-25T13:02:59.053 に答える
0

datetime 型を UNIX タイムスタンプに変換し、内容を json にエンコードします。

例: http://codepad.org/k3qF09Kr

于 2012-08-25T12:36:12.730 に答える
-2

カスタムエンコーダーを作成するだけです

(コールの答えへの小さいながらも重要な追加は、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

もちろん、これは、エンコードする前にデータフレームをより大きな辞書に入れた場合にも機能します。

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)
于 2015-12-28T07:23:00.627 に答える