web-dev-qa-db-fra.com

Sérialisation de la sortie vers JSON - ValueError: référence circulaire détectée

J'essaie de sortir les résultats de ma requête mysql en JSON. J'ai un problème avec la sérialisation du champ datetime.datetime, j'ai donc écrit une petite fonction pour le faire:

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

puis dans le code principal, je lance juste:

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)

Cependant, depuis que j'ai écrit la fonction date_handler, je reçois "ValueError: référence circulaire détectée"

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

Qu'est-ce que j'ai cassé? Existe-t-il un meilleur moyen de sérialiser la sortie en JSON?

32
pisarzp

La fonction que vous passez en tant qu'argument default ne sera appelée que pour les objets qui ne sont pas sérialisables en mode natif par le module json. Il doit renvoyer un objet sérialisable ou déclencher une TypeError.

Votre version renvoie le même objet que vous avez passé si ce n'est pas du type que vous corrigez (dates). Cela provoque l'erreur de référence circulaire (qui est trompeuse, car le cercle est entre un objet et lui-même après avoir été traité par date_handler).

Vous pouvez commencer à résoudre ce problème en modifiant date_handler pour déclencher une exception dans son bloc else. Cela échouera probablement encore, mais vous pourrez probablement découvrir quel objet se trouve dans votre structure de données à l'origine du problème en utilisant du code comme celui-ci:

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

Au lieu d'augmenter vous-même le TypeError, vous devriez relayer l'appel à la méthode par défaut de JSONEncoder:

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

Cela augmentera également TypeError et est une meilleure pratique, il permet à JSONEncoder d'essayer et de coder le type que votre méthode ne peut pas.

9
Joren Van Severen
json.dumps(obj, default=method_name)

La fonction "nom_méthode" doit renvoyer un objet sérialiser.

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