Сериализация вывода на JSON - ValueError: обнаружено круговое задание

Я пытаюсь вывести результаты моего запроса mysql в JSON. У меня проблема с сериализацией поля datetime.datetime, поэтому я написал небольшую функцию для этого:

def date_handler(obj):
    if hasattr(obj, 'isoformat'):
        return obj.isoformat()
    else:
        return obj

а затем в основном коде, который я запускаю:

products_json = []
for code in best_matching_codes:
    cur = db.cursor()
    query = "SELECT * FROM %s WHERE code LIKE '%s'" % (PRODUCTS_TABLE_NAME, product_code)
    cur.execute(query)
    columns = [desc[0] for desc in cur.description]
    rows = cur.fetchall()
    for row in rows:
        products_json.append(dict((k,v) for k,v in zip(columns,row)))   

return json.dumps(products_json, default = date_handler)

Однако, так как я написал функцию date_handler, я получаю "ValueError: обнаружено Circular reference"

127.0.0.1 - - [10/Jan/2013 00:42:18] "GET /1/product?code=9571%2F702 HTTP/1.1" 500 -
Traceback (most recent call last):
  File "/Library/Python/2.7/site-packages/flask/app.py", line 1701, in __call__
return self.wsgi_app(environ, start_response)
  File "/Library/Python/2.7/site-packages/flask/app.py", line 1689, in wsgi_app
response = self.make_response(self.handle_exception(e))
  File "/Library/Python/2.7/site-packages/flask/app.py", line 1687, in wsgi_app
response = self.full_dispatch_request()
  File "/Library/Python/2.7/site-packages/flask/app.py", line 1360, in full_dispatch_request
rv = self.handle_user_exception(e)
  File "/Library/Python/2.7/site-packages/flask/app.py", line 1358, in full_dispatch_request
rv = self.dispatch_request()
  File "/Library/Python/2.7/site-packages/flask/app.py", line 1344, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
  File "/Users/pisarzp/Desktop/SusyChoosy/susyAPI/test1.py", line 69, in product_search
return json.dumps(products_json, default = date_handler)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/__init__.py", line 238, in dumps
**kw).encode(obj)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/encoder.py", line 201, in encode
chunks = self.iterencode(o, _one_shot=True)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/encoder.py", line 264, in iterencode
return _iterencode(o, 0)
ValueError: Circular reference detected

Что я сломал? Есть ли лучший способ сериализации вывода в JSON?

Ответ 1

Функция, которую вы передаете как аргумент default, вызывается только для объектов, которые не могут быть сериализованы с помощью модуля json. Он должен возвращать сериализуемый объект или поднять TypeError.

Ваша версия возвращает тот же объект, который вы передали, если это не тот тип, который вы фиксируете (даты). Это вызывает круговую опорную ошибку (которая вводит в заблуждение, так как круг находится между одним объектом и им самим после обработки date_handler).

Вы можете исправить это, изменив date_handler, чтобы создать исключение в своем блоке else. Это все равно, вероятно, не удастся, но вы, возможно, затем можете узнать, какой именно объект находится в вашей структуре данных, что вызывает проблему с использованием кода следующим образом:

def date_handler(obj):
    if hasattr(obj, 'isoformat'):
        return obj.isoformat()
    else:
        raise TypeError(
            "Unserializable object {} of type {}".format(obj, type(obj))
        )

Ответ 2

Вместо того, чтобы поднять TypeError самостоятельно, вы должны передать вызов JSONEncoder по умолчанию-метод:

def date_handler(obj):
    if hasattr(obj, 'isoformat'):
        return obj.isoformat()
    else:
        json.JSONEncoder.default(self,obj)

Это также вызовет TypeError и является лучшей практикой, так как позволяет JSONEncoder пытаться закодировать тип, который ваш метод не может.

Ответ 3

json.dumps(obj, default=method_name)

Функция "method_name" должна возвращать объект сериализации.

def method_name(obj):
    data = {
            '__class__': obj.__class__.__name__,
            '__module__': obj.__module__
           }
    data.update(obj.__dict__)
    return data