web-dev-qa-db-fra.com

Json.dump échouant avec 'doit être unicode, pas str' TypeError

J'ai un fichier json qui contient une multitude de caractères chinois et japonais (et autres langues). Je le charge dans mon script python 2.7 en utilisant io.open Comme suit:

with io.open('multiIdName.json', encoding="utf-8") as json_data:
    cards = json.load(json_data)

J'ajoute une nouvelle propriété au json, tout va bien. Ensuite, j'essaie de l'écrire dans un autre fichier:

with io.open("testJson.json",'w',encoding="utf-8") as outfile:
        json.dump(cards, outfile, ensure_ascii=False)

C'est alors que j'obtiens l'erreur TypeError: must be unicode, not str

J'ai essayé d'écrire le fichier de sortie en binaire (with io.open("testJson.json",'wb') as outfile:), mais je me retrouve avec des trucs comme ceci:

{"multiverseid": 262906, "name": "\u00e6\u00b8\u00b8\u00e9\u009a\u00bc\u00e7\u008b\u00ae\u00e9\u00b9\u00ab", "language": "Chinese Simplified"}

Je pensais que l'ouvrir et l'écrire dans le même encodage serait suffisant, ainsi que le drapeau assure_ascii, mais clairement pas. Je veux juste conserver les caractères qui existaient dans le fichier avant d'exécuter mon script, sans qu'ils se transforment en\u.

17
IronWaffleMan

La raison de cette erreur est le comportement complètement stupide de json.dumps in Python 2:

>>> json.dumps({'a': 'a'}, ensure_ascii=False)
'{"a": "a"}'
>>> json.dumps({'a': u'a'}, ensure_ascii=False)
u'{"a": "a"}'
>>> json.dumps({'a': 'ä'}, ensure_ascii=False)
'{"a": "\xc3\xa4"}'
>>> json.dumps({u'a': 'ä'}, ensure_ascii=False)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python2.7/json/__init__.py", line 250, in dumps
    sort_keys=sort_keys, **kw).encode(obj)
  File "/usr/lib/python2.7/json/encoder.py", line 210, in encode
    return ''.join(chunks)
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 1: ordinal not in range(128)

Ceci, couplé au fait que io.open avec encoding set accepte uniquement les objets unicode (ce qui en soi a raison), entraîne des problèmes.

Le type de retour dépend complètement du type de clés ou de valeurs du dictionnaire, si ensure_ascii=False, mais str est toujours renvoyé si ensure_ascii=True. Si vous pouvez accidentellement définir des chaînes 8 bits en dictionnaires, vous ne pouvez pas convertir aveuglément ce type de retour en unicode, car vous avez besoin de pour définir l'encodage, vraisemblablement UTF-8:

>>> x = json.dumps(obj, ensure_ascii=False)
>>> if isinstance(x, str):
...     x = unicode(x, 'UTF-8')

Dans ce cas, je pense que vous pouvez utiliser le json.dump pour écrire dans un fichier binaire ouvert; cependant, si vous devez faire quelque chose de plus compliqué avec l'objet résultant, vous aurez probablement besoin du code ci-dessus.


Une solution consiste à mettre fin à toutes ces folies d'encodage/décodage en passant à Python 3.

5
Antti Haapala

Le module JSON gère l'encodage et le décodage pour vous, vous pouvez donc simplement ouvrir les fichiers d'entrée et de sortie en mode binaire. Le module JSON suppose le codage UTF-8, mais peut être modifié à l'aide de l'attribut encoding sur les méthodes load() et dump().

with open('multiIdName.json', 'rb') as json_data:
    cards = json.load(json_data)

ensuite:

with open("testJson.json", 'wb') as outfile:
    json.dump(cards, outfile, ensure_ascii=False)

Merci à @Antti Haapala, Python 2.x donne soit Unicode soit str selon le contenu de l'objet.

Vous devrez ajouter une vérification de sens pour vous assurer que le résultat est un Unicode avant d'écrire via io:

with io.open("testJson.json", 'w', encoding="utf-8") as outfile:
    my_json_str = json.dumps(my_obj, ensure_ascii=False)
    if isinstance(my_json_str, str):
        my_json_str = my_json_str.decode("utf-8")

    outfile.write(my_json_str)
3
Alastair McCormack