Python JSON编码器支持datetime?

时间:2012-08-25 12:33:41

标签: python mysql json ios5 tornado

有没有优雅的方法让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源代码。我能用自己的项目代码做些什么来实现这一目标吗?最好是一步到位。

非常感谢!

9 个答案:

答案 0 :(得分:19)

The docs suggest继承JSONEncoder并实现自己的默认方法。好像你基本上就在那里,而且这不是一个“肮脏的黑客”。

默认编码器未处理日期的原因是JSON中没有日期的标准表示。 Some people使用格式/Date(1198908717056)/,但我个人更喜欢ISO格式。

import datetime

class DateTimeEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, datetime.datetime):
            return obj.isoformat()
        elif isinstance(obj, datetime.date):
            return obj.isoformat()
        elif isinstance(obj, datetime.timedelta):
            return (datetime.datetime.min + obj).time().isoformat()
        else:
            return super(DateTimeEncoder, self).default(obj)

DateTimeEncoder().encode(object)

答案 1 :(得分:17)

json.dumps(thing, default=str)

答案 2 :(得分:8)

我为自己的项目制作了自己的课程:

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

参考文献:

注意:通过将类型为键和args,kwargs作为值的自定义字典传递给编码器的__init__()并在default()中使用它(或默认字典),可以使其更加灵活方法

答案 3 :(得分:3)

json.dumps(r, default=lambda o: o.isoformat() if hasattr(o, 'isoformat') else o)

答案 4 :(得分:1)

Tryton项目为datetime.datetimedatetime.datedatetime.time个对象(以及其他对象)提供了JSONEncoder实现。它用于服务器和客户端之间的JSON RPC通信。

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

答案 5 :(得分:0)

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

e.g。 : http://codepad.org/k3qF09Kr

答案 6 :(得分:0)

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

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

答案 7 :(得分:-1)

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

(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

当然,如果在编码之前将数据帧放入更大的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)

答案 8 :(得分:-1)

我建议使用ujson软件包或orjson软件包。

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