29

有什么优雅的方法可以让 Python JSON 编码器支持日期时间吗?一些第 3 方模块或简单的 hack?

我正在使用 tornado 的数据库包装器从 db 中获取一些行以生成 json。查询结果包括一个常规的 MySQL 时间戳列。

很烦人的是 Python 的默认 json 编码器不支持它自己的 datetime 类型,这在各种数据库查询中都很常见。

我不想修改 Python 自己的 json 编码器。有什么好的做法吗?非常感谢!

ps:我通过修改Python JSON编码器默认方法发现了一个肮脏的hack:

改变:

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)}

参考:

__init__()注意:通过将类型作为键和 args、kwargs 作为值的自定义字典传递给编码器并在方法中使用它(或默认字典),可以使其更加灵活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 项目有一个 JSONEncoder 实现,用于datetime.datetimedatetime.date对象datetime.time(与其他对象)。它用于服务器和客户端之间的 JSON RPC 通信。

http://hg.tryton.org/2.4/trytond/file/ade5432ac476/trytond/protocols/jsonrpc.py#l53

于 2012-08-25T13:02:59.053 回答
1

创建自定义解码器/编码器:

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
于 2019-05-27T12:17:59.547 回答
0

将 datetime 类型转换为 unix 时间戳,然后将内容编码为 json。

例如: http ://codepad.org/k3qF09Kr

于 2012-08-25T12:36:12.730 回答
-1

我建议使用ujson包装或orjson一个。

它们速度更快,并且仍然支持多种复杂类型。

于 2020-02-07T14:12:11.733 回答
-2

只需创建一个自定义编码器

(对 Cole 的回答的小而重要的补充是处理 pd.NaT (或空/空时间戳值),因为如果不添加,您将获得非常奇怪的 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 回答