JSON datetime между Python и JavaScript

393

Я хочу отправить объект datetime.datetime в сериализованной форме из Python, используя JSON, и десериализовать в JavaScript, используя JSON. Каков наилучший способ сделать это?

Питер Мортенсен
источник
Вы предпочитаете использовать библиотеку или вы хотите написать это самостоятельно?
Геттли

Ответы:

370

Вы можете добавить параметр 'default' в json.dumps, чтобы обработать это:

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

Обновление: добавлен вывод типа и значения.
Обновление: также обрабатывать дату

JT.
источник
11
Проблема в том, что если у вас есть другие объекты в списке / дикте, этот код преобразует их в None.
Томаш Высоцкий
5
json.dumps тоже не будет знать, как их конвертировать, но исключение исключается. К сожалению, лямбда-исправление в одну строку имеет свои недостатки. Если вы хотите получить исключение для неизвестных (что является хорошей идеей), используйте функцию, которую я добавил выше.
JT.
9
полный формат вывода также должен иметь часовой пояс ... и isoformat () не предоставляет эту функциональность ... поэтому вы должны обязательно добавить эту информацию в строку перед возвратом
Nick Franceschina
3
Это лучший способ пойти. Почему это не было выбрано в качестве ответа?
Брендон Кроуфорд
16
Лямбда может быть адаптирована для вызова базовой реализации для типов без даты и времени, поэтому TypeError может быть вызван при необходимости:dthandler = lambda obj: obj.isoformat() if isinstance(obj, datetime) else json.JSONEncoder().default(obj)
Pascal Bourque
81

Для кросс-языковых проектов я обнаружил, что строки с датами 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'))

К сожалению, конструктор Date в Javascript не принимает строки RfC 3339, но в Интернете доступно много синтаксических анализаторов .

huTools.hujson пытается обработать наиболее распространенные проблемы кодирования, с которыми вы можете столкнуться в коде Python, включая объекты даты / даты и времени, при правильной обработке часовых поясов.

Максимум
источник
17
Этот механизм форматирования даты изначально поддерживается как datetimedatetime.isoformat (), так и by simplejson, который по умолчанию создает дамп datetimeобъектов в виде isoformatстрок. Нет необходимости в ручном strftimeвзломе.
JRK
9
@jrk - я не получаю автоматическое преобразование datetimeобъектов в isoformatстроку. Для меня simplejson.dumps(datetime.now())урожайностьTypeError: datetime.datetime(...) is not JSON serializable
костмо
6
json.dumps(datetime.datetime.now().isoformat())Здесь происходит волшебство.
Яфанизм
2
Прелесть simplejson в том, что если у меня сложная структура данных, она будет анализировать ее и превращать в JSON. Если мне придется делать json.dumps (datetime.datetime.now (). Isoformat ()) для каждого объекта datetime, я теряю это. Есть ли способ это исправить?
andrewrk
1
superjoe30: см. stackoverflow.com/questions/455580/… о том, как это сделать,
максимум
67

Я решил это.

Допустим, у вас есть объект datetime Python, d , созданный с помощью datetime.now (). Его значение:

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

Вы можете сериализовать его в JSON в виде строки даты и времени ISO 8601:

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

Пример объекта datetime будет сериализован как:

'"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.datetime(2011, 5, 25, 20, 34, 5, 787000)
user240515
источник
50

Используя 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"]'
рамэн
источник
7
Незначительное улучшение - использование obj.isoformat(). Вы также можете использовать более распространенный dumps()вызов, который принимает другие полезные аргументы (например indent): simplejson.dumps (myobj, cls = JSONEncoder, ...)
rcoup
3
Потому что это вызвало бы метод родителя JSONEncoder, а не метод родителя DateTimeJSONEncoder. IE, вы бы поднялись на два уровня.
Брайан Арсуага
30

Вот довольно полное решение для рекурсивного кодирования и декодирования объектов datetime.datetime и datetime.date с использованием стандартного библиотечного jsonмодуля. Для этого требуется Python> = 2.6, поскольку %fкод формата в строке формата datetime.datetime.strptime () поддерживается только с тех пор. Для поддержки Python 2.5 удалите %fи удалите микросекунды из строки даты ISO, прежде чем пытаться преобразовать ее, но, конечно, вы потеряете точность в микросекундах. Для совместимости со строками даты ISO из других источников, которые могут включать имя часового пояса или смещение UTC, вам также может потребоваться удалить некоторые части строки даты перед преобразованием. Для полного анализатора для строк даты ISO (и многих других форматов даты) см. Сторонний модуль dateutil .

Декодирование работает только тогда, когда строки даты ISO являются значениями в буквенной нотации объектов JavaScript или во вложенных структурах внутри объекта. Строки даты ISO, которые являются элементами массива верхнего уровня, не будут декодированы.

Т.е. это работает:

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))
Крис Арндт
источник
Если вы напечатаете дату так, как datetime.datetime.utcnow().isoformat()[:-3]+"Z"это будет точно так же, как JSON.stringify () производит в javascript
w00t
24

Если вы уверены, что только Javascript будет использовать JSON, я предпочитаю передавать Dateобъекты Javascript напрямую.

ctime()Метод на datetimeобъектах возвращает строку , что дата объект Javascript может понять.

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

Javascript с радостью будет использовать его в качестве литерала объекта, и у вас уже есть встроенный объект Date.

Триптих
источник
12
Технически недопустимый JSON, но это действительный литерал объекта JavaScript. (Ради принципа я бы установил для Content-Type значение text / javascript вместо application / json.) Если потребитель всегда и всегда будет только реализацией JavaScript, то да, это довольно элегантно. Я бы использовал это.
система PAUSE
13
.ctime()это ОЧЕНЬ плохой способ передачи информации о времени, .isoformat()гораздо лучше. Что .ctime()отбрасывает часовой пояс и летнее время, как будто их не существует. Эта функция должна быть убита.
Евгений
Годы спустя: пожалуйста, не думайте об этом. Это будет работать только тогда, когда вы eval () ваш json в Javascript, который вы действительно не должны ...
domenukk
11

Поздно в игре ... :)

Очень простое решение - установить исправление по умолчанию для модуля json. Например:

import json
import datetime

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

Теперь вы можете использовать json.dumps (), как если бы он всегда поддерживал datetime ...

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

Это имеет смысл, если вам требуется, чтобы это расширение модуля json всегда включалось и вы не хотели менять способ, которым вы или другие используете сериализацию json (в существующем коде или нет).

Обратите внимание, что некоторые могут расценивать исправление библиотек таким образом как плохую практику. Особая осторожность должна быть предпринята, если вы захотите расширить свое приложение более чем одним способом - в таком случае я предлагаю использовать решение по ramen или JT и выбрать правильное расширение json в каждом случае.

davidhadas
источник
6
Это молча ест несериализуемые объекты и превращает их в None. Вы можете вместо этого бросить исключение.
Блендер
6

Не так много, чтобы добавить в сообщество ответ вики, кроме отметки времени !

Javascript использует следующий формат:

new Date().toJSON() // "2016-01-08T19:00:00.123Z"

Сторона Python (для json.dumpsобработчика, см. Другие ответы):

>>> from datetime import datetime
>>> d = datetime.strptime('2016-01-08T19:00:00.123Z', '%Y-%m-%dT%H:%M:%S.%fZ')
>>> d
datetime.datetime(2016, 1, 8, 19, 0, 0, 123000)
>>> d.isoformat() + 'Z'
'2016-01-08T19:00:00.123000Z'

Если вы оставите этот Z вне, каркасы внешнего интерфейса, такие как angular, не смогут отображать дату в местном часовом поясе браузера:

> $filter('date')('2016-01-08T19:00:00.123000Z', 'yyyy-MM-dd HH:mm:ss')
"2016-01-08 20:00:00"
> $filter('date')('2016-01-08T19:00:00.123000', 'yyyy-MM-dd HH:mm:ss')
"2016-01-08 19:00:00"
user1338062
источник
4

На стороне питона:

import time, json
from datetime import datetime as dt
your_date = dt.now()
data = json.dumps(time.mktime(your_date.timetuple())*1000)
return data # data send to javascript

На стороне JavaScript:

var your_date = new Date(data)

где данные являются результатом Python

Санк
источник
4

Мой совет - использовать библиотеку. Есть несколько доступных на pypi.org.

Я использую этот, он работает хорошо: https://pypi.python.org/pypi/asjson

guettli
источник
0

Видимо, «правильным» форматом даты JSON (хорошо JavaScript) является 2012-04-23T18: 25: 43.511Z - UTC и «Z». Без этого JavaScript будет использовать локальный часовой пояс веб-браузера при создании объекта Date () из строки.

Для «наивного» времени (которое Python называет временем без часового пояса и предполагает, что оно является локальным), приведенное ниже будет принудительно устанавливать местный часовой пояс, чтобы затем его можно было правильно преобразовать в UTC:

def default(obj):
    if hasattr(obj, "json") and callable(getattr(obj, "json")):
        return obj.json()
    if hasattr(obj, "isoformat") and callable(getattr(obj, "isoformat")):
        # date/time objects
        if not obj.utcoffset():
            # add local timezone to "naive" local time
            # /programming/2720319/python-figure-out-local-timezone
            tzinfo = datetime.now(timezone.utc).astimezone().tzinfo
            obj = obj.replace(tzinfo=tzinfo)
        # convert to UTC
        obj = obj.astimezone(timezone.utc)
        # strip the UTC offset
        obj = obj.replace(tzinfo=None)
        return obj.isoformat() + "Z"
    elif hasattr(obj, "__str__") and callable(getattr(obj, "__str__")):
        return str(obj)
    else:
        print("obj:", obj)
        raise TypeError(obj)

def dump(j, io):
    json.dump(j, io, indent=2, default=default)

Почему это так сложно.

Кэгни
источник
0

Для преобразования даты Python в JavaScript объект даты должен иметь определенный формат ISO, то есть формат ISO или номер UNIX. Если в формате ISO отсутствует некоторая информация, вы можете сначала преобразовать в число Unix с помощью Date.parse. Более того, Date.parse также работает с React, в то время как новый Date может вызвать исключение.

Если у вас есть объект DateTime без миллисекунд, необходимо учитывать следующее. :

  var unixDate = Date.parse('2016-01-08T19:00:00') 
  var desiredDate = new Date(unixDate).toLocaleDateString();

Примерная дата может также быть переменной в объекте result.data после вызова API.

Для вариантов отображения даты в желаемом формате (например, для отображения длинных дней недели) ознакомьтесь с документом MDN .

Патрик
источник