datetime/uuid is not json serializable

Update:

悲了个剧,又是一篇反转文,之前写的方案弱爆了,根本不需要自己去monkey patch JSONEncoder。
django已经内置了一个DjangoJSONEncoder,class JsonResponse用的就是它。
看看它的实现:

class DjangoJSONEncoder(json.JSONEncoder):
    """
    JSONEncoder subclass that knows how to encode date/time, decimal types and UUIDs.
    """
    def default(self, o):
        # See "Date Time String Format" in the ECMA-262 specification.
        if isinstance(o, datetime.datetime):
            r = o.isoformat()
            if o.microsecond:
                r = r[:23] + r[26:]
            if r.endswith('+00:00'):
                r = r[:-6] + 'Z'
            return r
        elif isinstance(o, datetime.date):
            return o.isoformat()
        elif isinstance(o, datetime.time):
            if is_aware(o):
                raise ValueError("JSON can't represent timezone-aware times.")
            r = o.isoformat()
            if o.microsecond:
                r = r[:12]
            return r
        elif isinstance(o, decimal.Decimal):
            return str(o)
        elif isinstance(o, uuid.UUID):
            return str(o)
        else:
            return super(DjangoJSONEncoder, self).default(o)

UUID、datetime全都考虑地妥妥滴,microsecond的截断也做了,哪里还需要你自己写蹩脚的代码去monkey patching……
用法:

from django.core.serializers.json import DjangoJSONEncoder
import json


json.dumps(dat, cls=DjangoJSONEncoder)

django项目中,序列化UUID到json,遇到了 UUID is not json serializable 的错误,
从这个链接找到了解决方案:
https://arthurpemberton.com/2015/04/fixing-uuid-is-not-json-serializable

然后依葫芦画瓢增加了对datetime的支持,并顺手将微秒部分截断掉,防止移动端解析的时候精度不够报错(只支持到毫秒),完整代码如下:

models.py

import uuid as UUID
import datetime


JSONEncoder_old_default = JSONEncoder.default
def JSONEncoder_new_default(self, o):
    if isinstance(o, UUID.UUID): return str(o)
    if isinstance(o, datetime.datetime) or isinstance(o, datetime.date):
        dt_str = o.isoformat()
        # truncate yyyy-MM-ddTHH:mm:ss.SSSSSSZ
        # --> yyyy-MM-ddTHH:mm:ss.SSSZ
        return dt_str[:23] + dt_str[26:]
    return JSONEncoder_old_default(self, o)
JSONEncoder.default = JSONEncoder_new_default

背后的故事
在没有对datetime的微秒部分进行截断之前,输出的json在安卓上无法通过Jackson解析,于是去github发了个issue:
https://github.com/FasterXML/jackson-databind/issues/1293

代码维护者说可以考虑写个字符串截断函数做个预处理,然后发Pull Request,我还真就去fork了源码,搜索了下哪里用到yyyy-MM-ddTHH:mm:ss.SSSSSSZ,加了个函数到src/main/java/com/fasterxml/jackson/databind/util/StdDateFormat.java:

private String truncateISO8601Microsecond(String str) {
        int dotPos = str.lastIndexOf('.');
        if (dotPos == -1)    // no fraction part
            return str;
        if (str.length() - 1 - dotPos < 6) // no microsecond part
            return str;

        // count for 6 digit-chars
        for (int i = 1; i < 7; i++) {
            char c = str.charAt(dotPos + i);
            if (c < '0' || c > '9') {
                return str; // non-digit char
            }
        }

        //truncate
        return str.substring(0, dotPos + 4) + str.substring(dotPos + 7);
    }

改完后发现除了StdDateFormat.java,另外还有个ISO8601Utils.java也用来处理Date……想想还是算了,理清楚这玩意还得花一番功夫,还是从源头解决算了。另外我对Java不熟悉,改了代码还得摸索如何打包测试,好烦躁,作罢吧。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据