Comment obtenir des objets chaîne au lieu d'Unicode à partir de JSON?

276

J'utilise Python 2 pour analyser JSON à partir de fichiers texte encodés ASCII .

Lors du chargement de ces fichiers avec jsonou simplejson, toutes mes valeurs de chaîne sont converties en objets Unicode au lieu d'objets chaîne. Le problème est que je dois utiliser les données avec certaines bibliothèques qui n'acceptent que les objets chaîne. Je ne peux pas changer les bibliothèques ni les mettre à jour.

Est-il possible d'obtenir des objets chaîne au lieu d'objets Unicode?

Exemple

>>> import json
>>> original_list = ['a', 'b']
>>> json_list = json.dumps(original_list)
>>> json_list
'["a", "b"]'
>>> new_list = json.loads(json_list)
>>> new_list
[u'a', u'b']  # I want these to be of type `str`, not `unicode`

Mettre à jour

Cette question a été posée il y a longtemps , lorsque j'étais coincé avec Python 2 . Une solution simple et claire pour aujourd'hui consiste à utiliser une version récente de Python, c'est-à-dire Python 3 et versions ultérieures .

Brutus
la source
1
Il n'y a aucun problème sous Python3, le type d'éléments dans new_list eststr
GoingMyWay
1
Python 3k n'est pas une «version récente de Python», c'est juste une branche alternative.
user2589273
11
C'est étrange de voir un tel commentaire en décembre 2017 - Python 2 est obsolète et aucune maintenance n'aura lieu après le 1er janvier 2020, soit moins de 2 ans: pythonclock.org
Zaar Hai
1
@ZaarHai BEAUCOUP de personnes sont coincées dans Python 2 contre leur gré. Il existe de nombreuses applications qui intègrent leur propre version Python pour l'automatisation et les scripts, donc les gens doivent l'utiliser jusqu'à la mise à jour du fournisseur (je vous regarde Maya, Houdini, Nuke ..)
Geordie
1
@Geordie Je le sais et je le comprends sûrement. Mon commentaire portait sur la terminologie - Python n'est pas une "branche alternative", mais plutôt un manque regrettable d'alternative (jeu de mots destiné) pour ceux qui sont coincés avec elle.
Zaar Hai

Réponses:

101

Une solution avec object_hook

import json

def json_load_byteified(file_handle):
    return _byteify(
        json.load(file_handle, object_hook=_byteify),
        ignore_dicts=True
    )

def json_loads_byteified(json_text):
    return _byteify(
        json.loads(json_text, object_hook=_byteify),
        ignore_dicts=True
    )

def _byteify(data, ignore_dicts = False):
    # if this is a unicode string, return its string representation
    if isinstance(data, unicode):
        return data.encode('utf-8')
    # if this is a list of values, return list of byteified values
    if isinstance(data, list):
        return [ _byteify(item, ignore_dicts=True) for item in data ]
    # if this is a dictionary, return dictionary of byteified keys and values
    # but only if we haven't already byteified it
    if isinstance(data, dict) and not ignore_dicts:
        return {
            _byteify(key, ignore_dicts=True): _byteify(value, ignore_dicts=True)
            for key, value in data.iteritems()
        }
    # if it's anything else, return it in its original form
    return data

Exemple d'utilisation:

>>> json_loads_byteified('{"Hello": "World"}')
{'Hello': 'World'}
>>> json_loads_byteified('"I am a top-level string"')
'I am a top-level string'
>>> json_loads_byteified('7')
7
>>> json_loads_byteified('["I am inside a list"]')
['I am inside a list']
>>> json_loads_byteified('[[[[[[[["I am inside a big nest of lists"]]]]]]]]')
[[[[[[[['I am inside a big nest of lists']]]]]]]]
>>> json_loads_byteified('{"foo": "bar", "things": [7, {"qux": "baz", "moo": {"cow": ["milk"]}}]}')
{'things': [7, {'qux': 'baz', 'moo': {'cow': ['milk']}}], 'foo': 'bar'}
>>> json_load_byteified(open('somefile.json'))
{'more json': 'from a file'}

Comment ça marche et pourquoi devrais-je l'utiliser?

La fonction de Mark Amery est plus courte et plus claire que celles-ci, alors quel est leur intérêt? Pourquoi voudriez-vous les utiliser?

Purement pour la performance . La réponse de Mark décode le texte JSON entièrement en premier avec des chaînes unicode, puis revient dans toute la valeur décodée pour convertir toutes les chaînes en chaînes d'octets. Cela a quelques effets indésirables:

  • Une copie de la structure décodée entière est créée en mémoire
  • Si votre objet JSON est vraiment profondément imbriqué (500 niveaux ou plus), vous atteindrez la profondeur de récursivité maximale de Python

Cette réponse atténue ces deux problèmes de performances en utilisant le object_hookparamètre de json.loadetjson.loads . De la documentation :

object_hookest une fonction facultative qui sera appelée avec le résultat de tout objet littéral décodé (a dict). La valeur de retour de object_hook sera utilisée à la place de dict. Cette fonctionnalité peut être utilisée pour implémenter des décodeurs personnalisés

Étant donné que les dictionnaires imbriqués de nombreux niveaux profondément dans d'autres dictionnaires sont passés à object_hook fur et mesure qu'ils sont décodés , nous pouvons byteifier toutes les chaînes ou listes à l'intérieur à ce stade et éviter la nécessité d'une récursivité profonde plus tard.

La réponse de Mark ne peut pas être utilisée telle object_hookquelle, car elle se reproduit dans des dictionnaires imbriqués. Nous empêchons cette récursivité dans cette réponse avec le ignore_dictsparamètre to _byteify, qui lui est transmis à tout moment sauf lorsqu'il lui object_hookpasse un nouveau dictà byteify. leignore_dicts drapeau indique _byteifyd'ignorer les dicts car ils ont déjà été byteifiés.

Enfin, nos implémentations de json_load_byteifiedet json_loads_byteifiedappelons _byteify(avec ignore_dicts=True) le résultat renvoyé par json.loadou json.loadspour gérer le cas où le texte JSON en cours de décodage n'a pas de dictniveau supérieur.

Mirec Miskuf
la source
1
+1 pour l'approche ici; Je ne l'ai pas vraiment saisi quand je l'ai lu pour la première fois, mais j'ai finalement compris en le relisant à la lumière de la réponse de Travis Jensen. J'ai fait un montage assez agressif dans l'espoir de clarifier comment cela fonctionne et quels sont ses avantages par rapport à ma réponse. L'idée centrale du code reste intacte, mais j'ai modifié à peu près tout le reste. N'hésitez pas à annuler ma modification si vous vous y opposez - c'est votre réponse!
Mark Amery
Pas de problème Mark, merci beaucoup. J'aime bien ton montage, il est beaucoup plus explicatif que mon original. Peut-être qu'un jour j'apprendrai à donner des réponses plus concises.
Mirec Miskuf
2
C'est une excellente solution; efficace et élégant. Cependant, si vous êtes coincé dans le domaine de Python <2.7, comme je le suis, vous devrez remplacer la ligne: return { byteify(key, ignore_dicts=True): _byteify(value, ignore_dicts=True) for key, value in data.iteritems() }avec return dict((_byteify(key, ignore_dicts=True), _byteify(value, ignore_dicts=True)) for key, value in data.iteritems())pour que cela fonctionne.
Richard Dunn
Je pense que vous vous trompez sur le problème de la profondeur de récursivité. Avec toi, je peux aller jusqu'à 990: json_loads_byteified('[' * 990 + ']' * 990). Avec 991, il plante. Marc travaille toujours avec 991: byteify(json.loads('[' * 991 + ']' * 991)). Il plante à 992. Donc, au moins dans ce test, Mark peut aller plus loin, contrairement à ce que vous avez dit.
Stefan Pochmann
@MarkAmery Que pensez-vous de mon commentaire ci-dessus? (Je viens de voir dans l'historique des modifications que c'est vous qui avez ajouté cette affirmation).
Stefan Pochmann
180

Bien qu'il y ait de bonnes réponses ici, j'ai fini par utiliser PyYAML pour analyser mes fichiers JSON, car il donne les clés et les valeurs sous strforme de chaînes de unicodetype au lieu de type. Parce que JSON est un sous-ensemble de YAML, il fonctionne bien:

>>> import json
>>> import yaml
>>> list_org = ['a', 'b']
>>> list_dump = json.dumps(list_org)
>>> list_dump
'["a", "b"]'
>>> json.loads(list_dump)
[u'a', u'b']
>>> yaml.safe_load(list_dump)
['a', 'b']

Remarques

Quelques points à noter cependant:

  • J'obtiens des objets chaîne car toutes mes entrées sont encodées en ASCII . Si j'utilisais des entrées codées unicode, je les récupérerais en tant qu'objets unicode - il n'y a pas de conversion!

  • Vous devriez (probablement toujours) utiliser la safe_loadfonction de PyYAML ; si vous l'utilisez pour charger des fichiers JSON, vous n'avez de toute façon pas besoin de la "puissance supplémentaire" de la loadfonction.

  • Si vous voulez un analyseur YAML qui prend davantage en charge la version 1.2 de la spécification (et analyse correctement des nombres très faibles ), essayez Ruamel YAML : pip install ruamel.yamlet import ruamel.yaml as yamlc'était tout ce dont j'avais besoin dans mes tests.

Conversion

Comme indiqué, il n'y a pas de conversion! Si vous ne pouvez pas être sûr de ne traiter que les valeurs ASCII (et vous ne pouvez pas être sûr la plupart du temps), mieux vaut utiliser une fonction de conversion :

J'ai utilisé celui de Mark Amery plusieurs fois maintenant, il fonctionne très bien et est très facile à utiliser. Vous pouvez également utiliser une fonction similaire à la object_hookplace, car cela pourrait vous permettre d' améliorer les performances des gros fichiers. Voir la réponse un peu plus complexe de Mirec Miskuf pour cela.

Brutus
la source
8
Faites attention si vous décidez d'utiliser cette réponse. Cela fonctionne parfaitement pour le cas de Brutus, mais uniquement parce qu'il sait que ses données ne contiennent que des caractères encodables en ASCII. Si vous n'avez pas cette garantie, cette réponse ne fonctionnera pas. Par exemple, essayez d'exécuter yaml.load(json.dumps([u'a', u'£', u'É']))sur le shell Python et observez que vous revenez ['a', u'\xa3', u'\xc9'](qui contient des unicodechaînes). Si vous ne pouvez pas être sûr que vos données ne contiennent que des caractères du jeu de caractères ASCII, vous devez utiliser une approche différente à la place (je recommande ma propre réponse).
Mark Amery
1
YAML utilise [u'a', u'b']également la prudence.
Carlos Calla
1
C'est bien, mais cela ne fonctionne pas avec des nombres faibles .. regardez ici: stackoverflow.com/questions/30458977/…
Oren
@Oren: Ce n'est pas une erreur dans la spécification YAML mais dans l'analyseur PyYAML. L' analyseur YAML de Ruamel fonctionne.
Brutus
Je veux avoir une sortie comme ["a", "b"] pas comme ['a', 'b'] @Brutus
user60679
141

Il n'y a pas d'option intégrée pour que les fonctions du module json retournent des chaînes d'octets au lieu de chaînes unicode. Cependant, cette fonction récursive courte et simple convertira tout objet JSON décodé de l'utilisation de chaînes unicode en chaînes d'octets encodées en UTF-8:

def byteify(input):
    if isinstance(input, dict):
        return {byteify(key): byteify(value)
                for key, value in input.iteritems()}
    elif isinstance(input, list):
        return [byteify(element) for element in input]
    elif isinstance(input, unicode):
        return input.encode('utf-8')
    else:
        return input

Appelez simplement cela sur la sortie que vous obtenez d'un json.loadou json.loadsappelez.

Quelques notes:

  • Pour prendre en charge Python 2.6 ou version antérieure, remplacez-le return {byteify(key): byteify(value) for key, value in input.iteritems()}par return dict([(byteify(key), byteify(value)) for key, value in input.iteritems()]), car les compréhensions de dictionnaire n'étaient pas prises en charge avant Python 2.7.
  • Étant donné que cette réponse se reproduit dans tout l'objet décodé, elle présente quelques caractéristiques de performances indésirables qui peuvent être évitées en utilisant très soigneusement les paramètres object_hookou object_pairs_hook. La réponse de Mirec Miskuf est jusqu'à présent la seule qui réussit à réussir cela, bien qu'en conséquence, c'est beaucoup plus compliqué que mon approche.
Mark Amery
la source
1
J'aime ça - ce n'est pas un ignorer - c'est reconnaître que lorsque les gens disent «chaînes» et «ascii», ils voulaient surtout naïvement vouloir des octets, pas des caractères unicode théoriques. (et pas ascii car ils veulent toujours des signes de livre à l'autre bout)
Danny Staple
J'aime cela, cela fonctionne presque de la même manière que ma jolie imprimante, car je sais que json ne fait pas de tuple, vous devriez également ajouter l'exception pour tuple.
y.petremann
Ceci est horriblement inefficace, vous obligeant à parcourir récursivement des nœuds dont vous n'avez peut-être pas besoin. Le module json vous donne des crochets pour le faire beaucoup plus efficacement. La réponse ci-dessous en utilisant object_hookest en réalité bien pire que celle-ci, mais, en utilisant object_pairs_hook, vous pouvez trouver une méthode raisonnablement efficace qui ne nécessite aucune récursivité ou réexamen des nœuds qui ne contiennent pas de chaînes.
Travis Jensen
1
@TravisJensen Intéressant. La object_pairs_hookméthode est peut-être très légèrement plus difficile à comprendre que celle-ci (vous devez comprendre comment le paramètre fonctionne et pourquoi les listes et les dict nécessitent une manipulation différente), et l'avantage de la performance n'aura pas d'importance pour la plupart des gens ... mais je m'attendrais à il existe, en particulier pour toute personne traitant un objet JSON anormalement profondément imbriqué.
Mark Amery
plus1 C'est la réponse la plus concise; d'ailleurs PyYAML est une peine à installer. La seule chose meilleure serait de micro-diffuser la conversion pour ne pas utiliser de mémoire 4X.
personal_cloud
74

Vous pouvez utiliser le object_hookparamètre pour json.loadspasser dans un convertisseur. Vous n'avez pas à effectuer la conversion après coup. Le jsonmodule transmettra toujours les object_hookdict uniquement, et il transmettra récursivement les dicts imbriqués, vous n'avez donc pas besoin de récursivement vous-même dans les dicts imbriqués. Je ne pense pas que je convertirais des chaînes unicode en nombres comme le montre Wells. S'il s'agit d'une chaîne unicode, elle a été citée en tant que chaîne dans le fichier JSON, elle est donc censée être une chaîne (ou le fichier est incorrect).

De plus, j'essaierais d'éviter de faire quelque chose comme str(val)sur un unicodeobjet. Vous devez utiliser value.encode(encoding)avec un encodage valide, selon ce que votre bibliothèque externe attend.

Ainsi, par exemple:

def _decode_list(data):
    rv = []
    for item in data:
        if isinstance(item, unicode):
            item = item.encode('utf-8')
        elif isinstance(item, list):
            item = _decode_list(item)
        elif isinstance(item, dict):
            item = _decode_dict(item)
        rv.append(item)
    return rv

def _decode_dict(data):
    rv = {}
    for key, value in data.iteritems():
        if isinstance(key, unicode):
            key = key.encode('utf-8')
        if isinstance(value, unicode):
            value = value.encode('utf-8')
        elif isinstance(value, list):
            value = _decode_list(value)
        elif isinstance(value, dict):
            value = _decode_dict(value)
        rv[key] = value
    return rv

obj = json.loads(s, object_hook=_decode_dict)
Mike Brennan
la source
3
C'est très bien si l'objet dans sest un JSON Object(une collection non ordonnée de paires clé: valeur avec le caractère ':' séparant la clé et la valeur, séparées par des virgules et entourées d'accolades), mais pas si c'est, disons, un JSON Array. Donc, si on donne un JSON Arraycomme ["a", "b"], le résultat sera toujours [u'a', u'b']. Aucun des autres paramètres de type de crochet de personnalisation actuellement disponibles pour json.loads()ne peut faire le travail non plus.
martineau
2
Comme, comme vous l'avez mentionné, le jsonmodule passera récursivement des dicts imbriqués , il n'est pas nécessaire de les vérifier dans les deux fonctions - les deux elifclauses qui les vérifient doivent donc être supprimées.
martineau
1
Notez que le démarrage des noms de fonction avec un trait de soulignement a une signification particulière pour les instructions d'importation. Si vous placez ces fonctions dans un fichier appelé Utility.py et dans un autre fichier from Utility import *, les fonctions ne seront pas visibles à cause de ce trait de soulignement.
M Katz
1
C'est vraiment une mauvaise idée. object_hookest appelé pour chaque objet json analysé, donc si vous récursivement dans ce qui vous est donné, vous «ré-octetez» les choses que vous avez déjà «byteifiées». Les performances vont croître géométriquement avec la taille de l'objet. J'ai inclus ici une réponse qui utilise object_pairs_hooket ne souffre pas de ce problème.
Travis Jensen
38

C'est parce que json n'a aucune différence entre les objets chaîne et les objets unicode. Ce sont toutes des chaînes en javascript.

Je pense que JSON a raison de renvoyer des objets Unicode . En fait, je n'accepterais rien de moins, car les chaînes javascript sont en fait des unicodeobjets (c'est-à-dire que les chaînes JSON (javascript) peuvent stocker tout type de caractère unicode), il est donc logique de créer des unicodeobjets lors de la traduction de chaînes à partir de JSON. Les chaînes simples ne conviennent tout simplement pas car la bibliothèque devrait deviner l'encodage que vous souhaitez.

Il vaut mieux utiliser unicodedes objets chaîne partout. Votre meilleure option est donc de mettre à jour vos bibliothèques afin qu'elles puissent traiter les objets Unicode.

Mais si vous voulez vraiment des bytestrings, encodez simplement les résultats dans l'encodage de votre choix:

>>> nl = json.loads(js)
>>> nl
[u'a', u'b']
>>> nl = [s.encode('utf-8') for s in nl]
>>> nl
['a', 'b']
nosklo
la source
Merci nosklo, c'est ce que j'ai fait en premier. Mais comme je l'ai dit, les données réelles que j'ai utilisées sont assez imbriquées et tout, donc cela a introduit une certaine tranquillité. Je suis toujours à la recherche d'une solution automatique ... Il y a au moins un rapport de bug où les gens se plaignent du simplejson renvoyant des objets chaîne au lieu d'unicode.
Brutus
1
@Brutus: Je pense que json a raison de renvoyer des objets Unicode. En fait, je n'accepterais rien de moins, car les chaînes javascript sont en fait des objets unicode. Ce que je veux dire, c'est que les chaînes json (javascript) peuvent stocker tout type de caractère unicode, il est donc logique de créer des objets unicode lors de la traduction à partir de json. Vous devriez vraiment réparer vos bibliothèques à la place.
nosklo
16

Il existe une solution de contournement facile.

TL; DR - Utilisez ast.literal_eval()au lieu de json.loads(). Les deux astet jsonsont dans la bibliothèque standard.

Bien que ce ne soit pas une réponse `` parfaite '', elle en obtient une assez loin si votre plan est d'ignorer complètement Unicode. En Python 2.7

import json, ast
d = { 'field' : 'value' }
print "JSON Fail: ", json.loads(json.dumps(d))
print "AST Win:", ast.literal_eval(json.dumps(d))

donne:

JSON Fail:  {u'field': u'value'}
AST Win: {'field': 'value'}

Cela devient plus velu lorsque certains objets sont vraiment des chaînes Unicode. La réponse complète devient rapidement poilue.

Charles Merriam
la source
11
Mieux être sûr que votre JSON ne contient pas null, trueou des falsevaleurs, parce qu'ils ne sont pas valides en Python et causeront literal_eval()à l' échec.
ʇsәɹoɈ
3
@ ʇsәɹoɈ Espérons également que votre JSON ne contienne pas de solidus échappé ( \/) à l'intérieur d'une chaîne, ou une séquence d'échappement unicode (comme "\u0061", qui est une autre façon d'écrire "a"). La syntaxe littérale de Python est incompatible avec JSON de plusieurs manières, et je ne ferais pas confiance à cette réponse pour un script que je n'allais pas jeter.
Mark Amery
Les gens ont raison de dire que si la chaîne est vraiment unicode, cette réponse échoue, mais si c'était le cas, nous ne serions pas en mesure de transtyper en chaîne de toute façon. +1 pour une réponse qui ne fonctionne que lorsqu'elle fonctionne et lève une exception dans le cas contraire
Stefan Sullivan
si possible ne pas utiliser jsonpour vider les données, utilisez simplement printsi vous exécutez python. Puis ast.literal_evaltravaille
Jean-François Fabre
11

La réponse de Mike Brennan est proche, mais il n'y a aucune raison de traverser à nouveau la structure entière. Si vous utilisez le object_hook_pairsparamètre (Python 2.7+):

object_pairs_hookest une fonction facultative qui sera appelée avec le résultat de tout objet littéral décodé avec une liste ordonnée de paires. La valeur de retour de object_pairs_hooksera utilisée à la place de dict. Cette fonctionnalité peut être utilisée pour implémenter des décodeurs personnalisés qui reposent sur l'ordre de décodage des paires clé / valeur (par exemple, collections.OrderedDictse souviendront de l'ordre d'insertion). Si object_hookest également défini, le object_pairs_hookprend la priorité.

Avec lui, vous obtenez chaque objet JSON, vous pouvez donc faire le décodage sans avoir besoin de récursivité:

def deunicodify_hook(pairs):
    new_pairs = []
    for key, value in pairs:
        if isinstance(value, unicode):
            value = value.encode('utf-8')
        if isinstance(key, unicode):
            key = key.encode('utf-8')
        new_pairs.append((key, value))
    return dict(new_pairs)

In [52]: open('test.json').read()
Out[52]: '{"1": "hello", "abc": [1, 2, 3], "def": {"hi": "mom"}, "boo": [1, "hi", "moo", {"5": "some"}]}'                                        

In [53]: json.load(open('test.json'))
Out[53]: 
{u'1': u'hello',
 u'abc': [1, 2, 3],
 u'boo': [1, u'hi', u'moo', {u'5': u'some'}],
 u'def': {u'hi': u'mom'}}

In [54]: json.load(open('test.json'), object_pairs_hook=deunicodify_hook)
Out[54]: 
{'1': 'hello',
 'abc': [1, 2, 3],
 'boo': [1, 'hi', 'moo', {'5': 'some'}],
 'def': {'hi': 'mom'}}

Notez que je n'ai jamais à appeler le crochet de manière récursive car chaque objet sera remis au crochet lorsque vous utiliserez le object_pairs_hook. Vous devez vous soucier des listes, mais comme vous pouvez le voir, un objet dans une liste sera correctement converti, et vous n'avez pas besoin de récursivement pour y arriver.

EDIT: Un collègue a souligné que Python2.6 n'a pas object_hook_pairs. Vous pouvez toujours utiliser cette volonté Python2.6 en faisant un très petit changement. Dans le crochet ci-dessus, modifiez:

for key, value in pairs:

à

for key, value in pairs.iteritems():

Utilisez ensuite object_hookau lieu de object_pairs_hook:

In [66]: json.load(open('test.json'), object_hook=deunicodify_hook)
Out[66]: 
{'1': 'hello',
 'abc': [1, 2, 3],
 'boo': [1, 'hi', 'moo', {'5': 'some'}],
 'def': {'hi': 'mom'}}

L'utilisation de object_pairs_hookrésultats dans un dictionnaire de moins est instanciée pour chaque objet dans l'objet JSON, ce qui, si vous analysiez un document volumineux, pourrait valoir la peine.

Travis Jensen
la source
1
C'est bien et semble très proche de mériter la coche verte (que Brutus a, admirablement, déjà largement diffusée car de meilleures réponses sont arrivées). Mais ... pourquoi ne pas gérer correctement les listes dans le document deunicodify_hookque vous présentez dans cette réponse? Pour le moment, vous avez une implémentation deunicodify_hookqui n'itère pas sur les listes et ne décode pas les chaînes et les listes qu'elles contiennent, et donc la sortie que vous exposez ne correspond pas à la sortie que votre hook va réellement produire. Corrigez cela, et cette réponse sera supérieure à la mienne.
Mark Amery
Frivole: Je suggérerais également de démontrer la fonction avec l'interpréteur CPython ordinaire plutôt que celui que vous utilisez ici (qui je pense est IronPython)? L'interpréteur CPython est plus familier à la plupart des utilisateurs de Python et est, à mon avis, plus joli.
Mark Amery
Cela ne fonctionne pas pour moi mais je suis sûr que c'est une bizarrerie de ce que je fais ... Je stocke une liste d'un plus grand document json dans un fichier. Que je le charge avec ou sans cet objet_pairs_hook, chaque élément apparaît unicode. Zut.
rsaw
1
@rsaw Bon point! Puisque le object_pairs_hookseul objet est appelé pour les objets , si votre texte JSON a une liste de chaînes au niveau supérieur, cette solution échouera. Il n'y a aucun moyen de résoudre ce problème sans appeler une fonction sur la chose retournée json.load; aucun des json.loadcrochets ne peut garantir que vous serez capable de gérer chaque chaîne. Je pense que c'est un assez gros défaut pour que je continue de recommander ma solution plutôt que d'utiliser les crochets.
Mark Amery
-1 parce que je viens de réaliser que Mirec Miskuf a déjà posté une réponse de hook d'objet qui n'a ni les inconvénients de l'approche de Mike Brennan (re-byteifie les mêmes dictionnaires plusieurs fois) ni de celle-ci (échoue à byteify les listes imbriquées ou les listes de niveau supérieur) ou cordes). Je ne sais pas pourquoi sa réponse a langui avec presque aucune attention alors que celle-ci - qui est inférieure - a rapidement gagné des voix.
Mark Amery
9

Je crains qu'il n'y ait aucun moyen d'y parvenir automatiquement dans la bibliothèque simplejson.

Le scanner et le décodeur de simplejson sont conçus pour produire du texte unicode. Pour ce faire, la bibliothèque utilise une fonction appelée c_scanstring(si elle est disponible, pour la vitesse), ou py_scanstringsi la version C n'est pas disponible. La scanstringfonction est appelée plusieurs fois par presque chaque routine que simplejson a pour décoder une structure qui pourrait contenir du texte. Vous devez soit monkeypatch la scanstringvaleur dans simplejson.decoder, ou sous JSONDecoder- classe et fournir à peu près votre propre implémentation entière de tout ce qui pourrait contenir du texte.

La raison pour laquelle simplejson génère unicode, cependant, est que la spécification json mentionne spécifiquement que "Une chaîne est une collection de zéro ou plusieurs caractères Unicode" ... la prise en charge de l'unicode est supposée faire partie du format lui-même. L' scanstringimplémentation de Simplejson va jusqu'à analyser et interpréter les échappements unicode (même en vérifiant les erreurs pour les représentations de jeux de caractères multi-octets mal formées), donc la seule façon dont il peut vous renvoyer la valeur de manière fiable est en tant qu'unicode.

Si vous avez une bibliothèque ancienne qui a besoin d'un str, je vous recommande soit de rechercher laborieusement la structure de données imbriquée après l'analyse (ce que je reconnais être ce que vous avez explicitement dit que vous vouliez éviter ... désolé), soit d'envelopper vos bibliothèques dans une sorte de façade où vous pouvez masser les paramètres d'entrée à un niveau plus granulaire. La deuxième approche pourrait être plus gérable que la première si vos structures de données sont en effet profondément imbriquées.

Jarret Hardie
la source
4

Comme Mark (Amery) le note correctement: L' utilisation du désérialiseur de PyYaml sur un vidage json ne fonctionne que si vous avez uniquement ASCII. Au moins hors de la boîte.

Deux commentaires rapides sur l'approche PyYaml:

  1. N'utilisez JAMAIS yaml.load sur les données du champ. C'est une fonctionnalité (!) De yaml pour exécuter du code arbitraire caché dans la structure.

  2. Vous pouvez le faire fonctionner également pour non ASCII via ceci:

    def to_utf8(loader, node):
        return loader.construct_scalar(node).encode('utf-8')
    yaml.add_constructor(u'tag:yaml.org,2002:str', to_utf8)

Mais la performance n'est pas comparable à la réponse de Mark Amery:

En lançant des exemples de dict profondément imbriqués sur les deux méthodes, j'obtiens ceci (avec dt [j] = delta temporel de json.loads (json.dumps (m))):

     dt[yaml.safe_load(json.dumps(m))] =~ 100 * dt[j]
     dt[byteify recursion(Mark Amery)] =~   5 * dt[j]

Donc, la désérialisation, y compris la marche complète de l'arbre et l' encodage, bien dans l'ordre de grandeur de l'implémentation basée sur C de json. Je trouve cela remarquablement rapide et aussi plus robuste que la charge de yaml sur les structures profondément imbriquées. Et moins sujet aux erreurs de sécurité, en regardant yaml.load.

=> Bien que j'apprécierais un pointeur vers un convertisseur basé uniquement sur C, la fonction byteify devrait être la réponse par défaut.

Cela est particulièrement vrai si votre structure json provient du champ, contenant des entrées utilisateur. Parce qu'alors vous devrez probablement marcher de toute façon sur votre structure - indépendamment de vos structures de données internes souhaitées («sandwich unicode» ou chaînes d'octets uniquement).

Pourquoi?

Normalisation Unicode . Pour les ignorants: prenez un analgésique et lisez ceci .

Donc, en utilisant la récursion byteify, vous tuez deux oiseaux avec une pierre:

  1. obtenez vos bytestrings à partir de dumps json imbriqués
  2. obtenir les valeurs d'entrée utilisateur normalisées, afin que vous trouviez les éléments dans votre stockage.

Lors de mes tests, il s'est avéré que remplacer le fichier input.encode ('utf-8') par un unicodedata.normalize ('NFC', input) .encode ('utf-8') était encore plus rapide que sans NFC - mais c'est fortement tributaire des données d'exemple, je suppose.

Pilule rouge
la source
3

Le gotcha est cela simplejsonet ce jsonsont deux modules différents, au moins dans la manière dont ils traitent avec l'unicode. Vous avez jsondans py 2.6+, et cela vous donne des valeurs unicode, tandis que simplejsonrenvoie des objets chaîne. Essayez simplement easy_install-ing simplejson dans votre environnement et voyez si cela fonctionne. Ça l'a fait pour moi.

ducu
la source
2

Utilisez simplement pickle au lieu de json pour le vidage et le chargement, comme ceci:

    import json
    import pickle

    d = { 'field1': 'value1', 'field2': 2, }

    json.dump(d,open("testjson.txt","w"))

    print json.load(open("testjson.txt","r"))

    pickle.dump(d,open("testpickle.txt","w"))

    print pickle.load(open("testpickle.txt","r"))

La sortie qu'il produit est (les chaînes et les entiers sont traités correctement):

    {u'field2': 2, u'field1': u'value1'}
    {'field2': 2, 'field1': 'value1'}
Stefan Gruenwald
la source
1
+1 pour une solution qui ne nécessite pas de packages supplémentaires (comme yaml ). Mais parfois - comme dans mon cas d'origine - j'ai besoin d'avoir les données en JSON, donc le cornichon n'est pas toujours la meilleure option. D'ailleurs, vous en avez safe_loaddans YAML, je ne sais pas si quelque chose de similaire existe pour les cornichons .
Brutus
1

J'ai donc rencontré le même problème. Devinez quel a été le premier résultat Google.

Comme je dois transmettre toutes les données à PyGTK, les chaînes unicode ne me sont pas très utiles non plus. J'ai donc une autre méthode de conversion récursive. Il est en fait également nécessaire pour la conversion JSON de typeafe - json.dump () serait mis en place sur tous les non-littéraux, comme les objets Python. Ne convertit cependant pas les index dict.

# removes any objects, turns unicode back into str
def filter_data(obj):
        if type(obj) in (int, float, str, bool):
                return obj
        elif type(obj) == unicode:
                return str(obj)
        elif type(obj) in (list, tuple, set):
                obj = list(obj)
                for i,v in enumerate(obj):
                        obj[i] = filter_data(v)
        elif type(obj) == dict:
                for i,v in obj.iteritems():
                        obj[i] = filter_data(v)
        else:
                print "invalid object in data, converting to string"
                obj = str(obj) 
        return obj
mario
la source
Le seul problème qui pourrait survenir ici est si vous avez besoin des clés d'un dictionnaire converti à partir d'unicode. Bien que cette implémentation convertisse les valeurs, elle conserve les clés unicode. Si vous créez un 'newobj', utilisez newobj [str (i)] = ... et affectez obj = newobj lorsque vous avez terminé, les clés seront également converties.
Neal Stublen
Cela pourrait être plus joli avec des compréhensions ou mieux en convertissant des clés. C'est aussi unidiomatique; il mute à la fois les objets en place (dans le cas des dictionnaires) et renvoie la nouvelle valeur, ce qui est incompatible avec les méthodes de collecte intégrées de Python qui mutent l'objet actuel ou en retournent un nouveau, mais pas les deux.
Mark Amery
1

J'avais un dict JSON comme chaîne. Les clés et les valeurs étaient des objets Unicode comme dans l'exemple suivant:

myStringDict = "{u'key':u'value'}"

Je pourrais utiliser la byteifyfonction suggérée ci-dessus en convertissant la chaîne en un dictobjet en utilisant ast.literal_eval(myStringDict).

narko
la source
L'exemple que vous avez donné n'est pas un exemple de JSON. {u'key':u'value'}n'est pas JSON.
Mark Amery
2
Je sais parfaitement que ce n'est pas JSON. Voilà comment il a été analysé à partir d'une source externe dans mon script python. Si c'était JSON directement comme dans l'exemple suivant, je n'aurais pas besoin de la fonction byteify marquée comme solution: {"firstName": "John", "lastName": "Doe"}. Ce serait génial si, avant de voter, vous lisiez les réponses. Merci.
narko
1

Prise en charge de Python2 et 3 à l'aide du crochet (depuis https://stackoverflow.com/a/33571117/558397 )

import requests
import six
from six import iteritems

requests.packages.urllib3.disable_warnings()  # @UndefinedVariable
r = requests.get("http://echo.jsontest.com/key/value/one/two/three", verify=False)

def _byteify(data):
    # if this is a unicode string, return its string representation
    if isinstance(data, six.string_types):
        return str(data.encode('utf-8').decode())

    # if this is a list of values, return list of byteified values
    if isinstance(data, list):
        return [ _byteify(item) for item in data ]

    # if this is a dictionary, return dictionary of byteified keys and values
    # but only if we haven't already byteified it
    if isinstance(data, dict):
        return {
            _byteify(key): _byteify(value) for key, value in iteritems(data)
        }
    # if it's anything else, return it in its original form
    return data

w = r.json(object_hook=_byteify)
print(w)

Retour:

 {'three': '', 'key': 'value', 'one': 'two'}
abarik
la source
0

C'est la fin du jeu, mais j'ai construit ce lanceur récursif. Cela fonctionne pour mes besoins et je pense que c'est relativement complet. Cela peut vous aider.

def _parseJSON(self, obj):
    newobj = {}

    for key, value in obj.iteritems():
        key = str(key)

        if isinstance(value, dict):
            newobj[key] = self._parseJSON(value)
        elif isinstance(value, list):
            if key not in newobj:
                newobj[key] = []
                for i in value:
                    newobj[key].append(self._parseJSON(i))
        elif isinstance(value, unicode):
            val = str(value)
            if val.isdigit():
                val = int(val)
            else:
                try:
                    val = float(val)
                except ValueError:
                    val = str(val)
            newobj[key] = val

    return newobj

Passez-lui simplement un objet JSON comme ceci:

obj = json.loads(content, parse_float=float, parse_int=int)
obj = _parseJSON(obj)

Je l'ai en tant que membre privé d'une classe, mais vous pouvez réutiliser la méthode comme bon vous semble.

puits
la source
J'ai rencontré un problème où j'essaie d'analyser JSON et de passer le mappage résultant à une fonction en tant que ** kwargs. Il semble que les noms des paramètres de fonction ne puissent pas être unicode, donc votre fonction _parseJSON est géniale. S'il existe un moyen plus simple, quelqu'un peut me le faire savoir.
Neal Stublen
1
Ce code a un problème - vous effectuez un appel récursif dans l'élément List, qui échouera si les éléments de la liste ne sont pas eux-mêmes des dictionnaires.
I82
Outre le bogue décrit par @ I82Much, il est également mal nommé (il n'analyse pas réellement le JSON; un json.loadsappel est d'abord nécessaire), essaie arbitrairement de convertir des chaînes en entiers sans raison expliquée, et n'est pas copier-et- coller prêt.
Mark Amery
0

J'ai réécrit _parse_json () de Wells pour gérer les cas où l'objet json lui-même est un tableau (mon cas d'utilisation).

def _parseJSON(self, obj):
    if isinstance(obj, dict):
        newobj = {}
        for key, value in obj.iteritems():
            key = str(key)
            newobj[key] = self._parseJSON(value)
    elif isinstance(obj, list):
        newobj = []
        for value in obj:
            newobj.append(self._parseJSON(value))
    elif isinstance(obj, unicode):
        newobj = str(obj)
    else:
        newobj = obj
    return newobj
darnmarshall
la source
0

voici un encodeur récursif écrit en C: https://github.com/axiros/nested_encode

Surcoût de performance pour les structures "moyennes" d'environ 10% par rapport à json.loads.

python speed.py                                                                                            
  json loads            [0.16sec]: {u'a': [{u'b': [[1, 2, [u'\xd6ster..
  json loads + encoding [0.18sec]: {'a': [{'b': [[1, 2, ['\xc3\x96ster.
  time overhead in percent: 9%

en utilisant cette structure de test:

import json, nested_encode, time

s = """
{
  "firstName": "Jos\\u0301",
  "lastName": "Smith",
  "isAlive": true,
  "age": 25,
  "address": {
    "streetAddress": "21 2nd Street",
    "city": "\\u00d6sterreich",
    "state": "NY",
    "postalCode": "10021-3100"
  },
  "phoneNumbers": [
    {
      "type": "home",
      "number": "212 555-1234"
    },
    {
      "type": "office",
      "number": "646 555-4567"
    }
  ],
  "children": [],
  "spouse": null,
  "a": [{"b": [[1, 2, ["\\u00d6sterreich"]]]}]
}
"""


t1 = time.time()
for i in xrange(10000):
    u = json.loads(s)
dt_json = time.time() - t1

t1 = time.time()
for i in xrange(10000):
    b = nested_encode.encode_nested(json.loads(s))
dt_json_enc = time.time() - t1

print "json loads            [%.2fsec]: %s..." % (dt_json, str(u)[:20])
print "json loads + encoding [%.2fsec]: %s..." % (dt_json_enc, str(b)[:20])

print "time overhead in percent: %i%%"  % (100 * (dt_json_enc - dt_json)/dt_json)
Pilule rouge
la source
0

Avec Python 3.6, parfois je rencontre toujours ce problème. Par exemple, lorsque j'obtiens une réponse d'une API REST et que je charge le texte de la réponse dans JSON, j'obtiens toujours les chaînes unicode. Trouvé une solution simple en utilisant json.dumps ().

response_message = json.loads(json.dumps(response.text))
print(response_message)
Yuelin
la source
-1

J'ai également rencontré ce problème, et devant faire face à JSON, j'ai trouvé une petite boucle qui convertit les clés unicode en chaînes. ( simplejsonsur GAE ne renvoie pas de clés de chaîne.)

obj est l'objet décodé à partir de JSON:

if NAME_CLASS_MAP.has_key(cls):
    kwargs = {}
    for i in obj.keys():
        kwargs[str(i)] = obj[i]
    o = NAME_CLASS_MAP[cls](**kwargs)
    o.save()

kwargsest ce que je passe au constructeur de l'application GAE (qui n'aime pas les unicodeclés **kwargs)

Pas aussi robuste que la solution de Wells, mais beaucoup plus petit.

codeur de bateau
la source
-1

J'ai adapté le code de la réponse de Mark Amery , notamment pour se débarrasser des isinstancepros de la frappe de canard.

L'encodage se fait manuellement et ensure_asciiest désactivé. La documentation de python pour json.dumpdit que

Si Ensure_ascii est True (par défaut), tous les caractères non ASCII dans la sortie sont échappés avec des séquences \ uXXXX

Avertissement: dans le doctorat, j'ai utilisé la langue hongroise. Certains encodages de caractères liés à la Hongrie notables sont: cp852l'encodage IBM / OEM utilisé par exemple. sous DOS (parfois appelé ascii , à tort je pense, cela dépend du réglage de la page de code ), cp1250utilisé par exemple. sous Windows (parfois appelé ansi , en fonction des paramètres régionaux), et iso-8859-2parfois utilisé sur les serveurs http. Le texte du test Tüskéshátú kígyóbűvölőest attribué à Koltai László (forme de nom personnel natif) et provient de wikipedia .

# coding: utf-8
"""
This file should be encoded correctly with utf-8.
"""
import json

def encode_items(input, encoding='utf-8'):
    u"""original from: https://stackoverflow.com/a/13101776/611007
    adapted by SO/u/611007 (20150623)
    >>> 
    >>> ## run this with `python -m doctest <this file>.py` from command line
    >>> 
    >>> txt = u"Tüskéshátú kígyóbűvölő"
    >>> txt2 = u"T\\u00fcsk\\u00e9sh\\u00e1t\\u00fa k\\u00edgy\\u00f3b\\u0171v\\u00f6l\\u0151"
    >>> txt3 = u"uúuutifu"
    >>> txt4 = b'u\\xfauutifu'
    >>> # txt4 shouldn't be 'u\\xc3\\xbauutifu', string content needs double backslash for doctest:
    >>> assert u'\\u0102' not in b'u\\xfauutifu'.decode('cp1250')
    >>> txt4u = txt4.decode('cp1250')
    >>> assert txt4u == u'u\\xfauutifu', repr(txt4u)
    >>> txt5 = b"u\\xc3\\xbauutifu"
    >>> txt5u = txt5.decode('utf-8')
    >>> txt6 = u"u\\u251c\\u2551uutifu"
    >>> there_and_back_again = lambda t: encode_items(t, encoding='utf-8').decode('utf-8')
    >>> assert txt == there_and_back_again(txt)
    >>> assert txt == there_and_back_again(txt2)
    >>> assert txt3 == there_and_back_again(txt3)
    >>> assert txt3.encode('cp852') == there_and_back_again(txt4u).encode('cp852')
    >>> assert txt3 == txt4u,(txt3,txt4u)
    >>> assert txt3 == there_and_back_again(txt5)
    >>> assert txt3 == there_and_back_again(txt5u)
    >>> assert txt3 == there_and_back_again(txt4u)
    >>> assert txt3.encode('cp1250') == encode_items(txt4, encoding='utf-8')
    >>> assert txt3.encode('utf-8') == encode_items(txt5, encoding='utf-8')
    >>> assert txt2.encode('utf-8') == encode_items(txt, encoding='utf-8')
    >>> assert {'a':txt2.encode('utf-8')} == encode_items({'a':txt}, encoding='utf-8')
    >>> assert [txt2.encode('utf-8')] == encode_items([txt], encoding='utf-8')
    >>> assert [[txt2.encode('utf-8')]] == encode_items([[txt]], encoding='utf-8')
    >>> assert [{'a':txt2.encode('utf-8')}] == encode_items([{'a':txt}], encoding='utf-8')
    >>> assert {'b':{'a':txt2.encode('utf-8')}} == encode_items({'b':{'a':txt}}, encoding='utf-8')
    """
    try:
        input.iteritems
        return {encode_items(k): encode_items(v) for (k,v) in input.iteritems()}
    except AttributeError:
        if isinstance(input, unicode):
            return input.encode(encoding)
        elif isinstance(input, str):
            return input
        try:
            iter(input)
            return [encode_items(e) for e in input]
        except TypeError:
            return input

def alt_dumps(obj, **kwargs):
    """
    >>> alt_dumps({'a': u"T\\u00fcsk\\u00e9sh\\u00e1t\\u00fa k\\u00edgy\\u00f3b\\u0171v\\u00f6l\\u0151"})
    '{"a": "T\\xc3\\xbcsk\\xc3\\xa9sh\\xc3\\xa1t\\xc3\\xba k\\xc3\\xadgy\\xc3\\xb3b\\xc5\\xb1v\\xc3\\xb6l\\xc5\\x91"}'
    """
    if 'ensure_ascii' in kwargs:
        del kwargs['ensure_ascii']
    return json.dumps(encode_items(obj), ensure_ascii=False, **kwargs)

Je voudrais également souligner la réponse de Jarret Hardie qui fait référence à la spécification JSON , citant:

Une chaîne est une collection de zéro ou plusieurs caractères Unicode

Dans mon cas d'utilisation, j'avais des fichiers avec json. Ce sont utf-8des fichiers encodés. ensure_asciise traduit par des fichiers json correctement échappés mais peu lisibles, c'est pourquoi j'ai adapté la réponse de Mark Amery à mes besoins.

Le docteur n'est pas particulièrement réfléchi mais je partage le code dans l'espoir qu'il sera utile à quelqu'un.

n611x007
la source
Je ne suis pas sûr de voir les avantages de l'utilisation de la saisie de canard ici? Nous savons que les collections renvoyées json.loadsseront des listes ou des dict, pas un type défini par l'utilisateur ou défini par la bibliothèque qui implémente leurs méthodes et méthodes magiques, alors pourquoi ne pas simplement faire une isinstancevérification? N'est-ce pas plus facile à comprendre que de vérifier l'existence iteritemsou iterl'acceptation de l'objet comme argument?
Mark Amery
@MarkAmery, il s'agit de vidages, pas de charges. si vous créez des données à vider - au lieu de les charger - vous ne pouvez pas être sûr de ce que c'est. l'idée était de le laisser venir de n'importe où dans le code.
n611x007
-2

Découvrez cette réponse à une question similaire comme celle-ci qui dit que

Le préfixe u signifie simplement que vous avez une chaîne Unicode. Lorsque vous utilisez vraiment la chaîne, elle n'apparaîtra pas dans vos données. Ne soyez pas éjecté par la sortie imprimée.

Par exemple, essayez ceci:

print mail_accounts[0]["i"]

Vous ne verrez pas un u.

kunal
la source
Ce n'est pas vrai si par exemple vous voulez formater quelque chose contenant une chaîne unicode, en Py2. par exemple, '{}'.format({u'x' : u'y'})comprend toujours les u.
Ponkadoodle