網站搜尋

Python 與 JavaScript 之間的 JSON 日期時間


我想使用 JSON 從 Python 傳送一個序列化的 datetime.datetime 對象,並在 JavaScript 中使用 JSON 進行反序列化。最好的方法是什麼?

答案#1

你可以在 json.dumps 中添加 'default' 參數來處理這個問題:

date_handler = lambda obj: (
    obj.isoformat()
    if isinstance(obj, (datetime.datetime, datetime.date))
    else None
)
json.dumps(datetime.datetime.now(), default=date_handler)
'"2010-04-20T20:08:21.634121"'

這是 ISO 8601 格式。

一個更全面的預設處理函數:

def handler(obj):
    if hasattr(obj, 'isoformat'):
        return obj.isoformat()
    elif isinstance(obj, ...):
        return ...
    else:
        raise TypeError, 'Object of type %s with value of %s is not JSON serializable' % (type(obj), repr(obj))

更新:新增輸出類型以及數值。
更新:也處理日期

答案#2

對於跨語言專案,我發現包含 RFC 3339 日期的字串是最好的方法。RFC 3339 日期看起來像這樣:

  1985-04-12T23:20:50.52Z

我認為大多數格式都是明顯的。唯一略微不尋常的可能是結尾的“Z”。它代表 GMT/UTC。你也可以加上一個時區偏移,例如 +02:00 代表 CEST(德國夏季)。我個人傾向於在顯示之前保持所有時間在 UTC。

在顯示、比較和儲存時,你可以在所有語言中保留它為字串格式。如果你需要用於計算,通常可以很容易地將它轉回大多數語言的本地日期物件。

所以要像這樣生成 JSON:

  json.dump(datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ'))

不幸的是,JavaScript 的 Date 建構函數不接受 RFC 3339 字串,但網路上有許多解析器可用。

huTools.hujson 嘗試處理你在 Python 代碼中可能遇到的最常見編碼問題,包括在正確處理時區的同時處理日期/日期時間對象。

答案#3

我已經弄清楚了。

假設你有一個使用 datetime.now() 創建的 Python datetime 物件 d。它的值是:

datetime.datetime(2011, 5, 25, 13, 34, 5, 787000)

您可以將其序列化為 ISO 8601 日期時間字串的 JSON:

import json
json.dumps(d.isoformat())

範例的日期時間物件將被序列化為:

'"2011-05-25T13:34:05.787000"'

這個值一旦在 JavaScript 層接收到,就可以用來建立一個 Date 物件:

var d = new Date("2011-05-25T13:34:05.787000");

自 JavaScript 1.8.5 起,Date 物件具有 toJSON 方法,該方法會返回一個標準格式的字串。因此,要將上述 JavaScript 物件序列化回 JSON,指令將是:

d.toJSON()

這將給你:

'2011-05-25T20:34:05.787Z'

這個字串,一旦在 Python 中接收到,就可以反序列化回 datetime 物件:

datetime.strptime('2011-05-25T20:34:05.787Z', '%Y-%m-%dT%H:%M:%S.%fZ')

這會產生以下的日期時間物件,這與你一開始使用的相同,因此是正確的:

datetime.datetime(2011, 5, 25, 20, 34, 5, 787000)

答案#4

使用 json,你可以繼承 JSONEncoder 並覆寫 default() 方法來提供你自己的自定義序列化器:

import json
import datetime

class DateTimeJSONEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, datetime.datetime):
            return obj.isoformat()
        else:
            return super(DateTimeJSONEncoder, self).default(obj)

然後,你可以這樣呼叫它:

>>> DateTimeJSONEncoder().encode([datetime.datetime.now()])
'["2010-06-15T14:42:28"]'

答案#5

這裡提供了一個相當完整的解決方案,用於遞歸編碼和解碼 datetime.datetime 和 datetime.date 對象,使用標準庫 json 模組。這需要 Python >= 2.6,因為 datetime.datetime.strptime() 格式字串中的 %f 格式代碼僅從那時起被支持。對於 Python 2.5 支持,請刪除 %f 並在嘗試轉換之前從 ISO 8601 日期字串中去掉微秒,當然,這會失去微秒精度。為了與來自其他來源的 ISO 8601 日期字串互操作,這些字串可能包含時區名稱或 UTC 偏移量,你可能還需要在轉換之前去掉日期字串中的某些部分。欲獲取完整的 ISO 8601 日期字串解析器(以及許多其他日期格式),請參閱第三方的 dateutil 模組。

解碼僅在 ISO 8601 日期字串是 JavaScript 文字物件表示的值,或是物件內嵌結構中的值時才有效。作為頂層陣列項目的 ISO 8601 日期字串將 不會 被解碼。

也就是,這個可行:

date = datetime.datetime.now()
>>> json = dumps(dict(foo='bar', innerdict=dict(date=date)))
>>> json
'{"innerdict": {"date": "2010-07-15T13:16:38.365579"}, "foo": "bar"}'
>>> loads(json)
{u'innerdict': {u'date': datetime.datetime(2010, 7, 15, 13, 16, 38, 365579)},
u'foo': u'bar'}

還有這個:

>>> json = dumps(['foo', 'bar', dict(date=date)])
>>> json
'["foo", "bar", {"date": "2010-07-15T13:16:38.365579"}]'
>>> loads(json)
[u'foo', u'bar', {u'date': datetime.datetime(2010, 7, 15, 13, 16, 38, 365579)}]

但是這並不像預期那樣運作:

>>> json = dumps(['foo', 'bar', date])
>>> json
'["foo", "bar", "2010-07-15T13:16:38.365579"]'
>>> loads(json)
[u'foo', u'bar', u'2010-07-15T13:16:38.365579']

這是代碼:

__all__ = ['dumps', 'loads']

import datetime

try:
    import json
except ImportError:
    import simplejson as json

class JSONDateTimeEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, (datetime.date, datetime.datetime)):
            return obj.isoformat()
        else:
            return json.JSONEncoder.default(self, obj)

def datetime_decoder(d):
    if isinstance(d, list):
        pairs = enumerate(d)
    elif isinstance(d, dict):
        pairs = d.items()
    result = []
    for k,v in pairs:
        if isinstance(v, basestring):
            try:
                # The %f format code is only supported in Python >= 2.6.
                # For Python <= 2.5 strip off microseconds
                # v = datetime.datetime.strptime(v.rsplit('.', 1)[0],
                #     '%Y-%m-%dT%H:%M:%S')
                v = datetime.datetime.strptime(v, '%Y-%m-%dT%H:%M:%S.%f')
            except ValueError:
                try:
                    v = datetime.datetime.strptime(v, '%Y-%m-%d').date()
                except ValueError:
                    pass
        elif isinstance(v, (dict, list)):
            v = datetime_decoder(v)
        result.append((k, v))
    if isinstance(d, list):
        return [x[1] for x in result]
    elif isinstance(d, dict):
        return dict(result)

def dumps(obj):
    return json.dumps(obj, cls=JSONDateTimeEncoder)

def loads(obj):
    return json.loads(obj, object_hook=datetime_decoder)

if __name__ == '__main__':
    mytimestamp = datetime.datetime.utcnow()
    mydate = datetime.date.today()
    data = dict(
        foo = 42,
        bar = [mytimestamp, mydate],
        date = mydate,
        timestamp = mytimestamp,
        struct = dict(
            date2 = mydate,
            timestamp2 = mytimestamp
        )
    )

    print repr(data)
    jsonstring = dumps(data)
    print jsonstring
    print repr(loads(jsonstring))

答案#6

如果你確定只有 JavaScript 會使用這個 JSON,我偏好直接傳遞 JavaScript 的 Date 物件。

datetime 物件上的 ctime() 方法會回傳一個 JavaScript Date 物件可以理解的字串。

import datetime
date = datetime.datetime.today()
json = '{"mydate":new Date("%s")}' % date.ctime()

JavaScript 會很高興地將其用作物件字面值,而你的 Date 物件已經內建完成。

答案#7

一個非常簡單的解決方法是修補 json 模組的預設設定。

例如:

import json
import datetime

json.JSONEncoder.default = lambda self,obj: (obj.isoformat() if isinstance(obj, datetime.datetime) else None)

現在,你可以像它一直支持 datetime 一樣使用 json.dumps()…

json.dumps({'created':datetime.datetime.now()})

如果你需要這個對 json 模組的擴展總是生效,並且希望不改變你或其他使用者使用 JSON 序列化的方式(無論是在現有代碼中還是其他情況下),這是合理的。

請注意,有些人可能認為以那種方式修補函式庫是不良的做法。

如果你可能希望以多種方式擴展你的應用程序,則需要特別小心。在這種情況下,我建議使用 ramen 或 JT 的解決方案,並在每種情況下選擇合適的 JSON 擴展。