J'utilise Python 2 pour analyser JSON à partir de fichiers texte codés ASCII.
Lors du chargement de ces fichiers avec json
ou 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 de ceux Unicode?
>>> 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`
Cette question a été posée il y a longtemps}, lorsque j'étais coincé avec Python 2. Une solution simple et propre pour aujourd’hui consiste à utiliser une version récente de Python, à savoir Python 3 et suivante.
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'}
La fonction de Mark Amery est plus courte et plus claire que celles-ci, alors à quoi ça sert? Pourquoi voudriez-vous les utiliser?
Purement pour performance. La réponse de Mark décode entièrement le texte JSON d'abord avec des chaînes unicode, puis revient à travers la valeur décodée complète pour convertir toutes les chaînes en chaînes d'octets. Cela a quelques effets indésirables:
Cette réponse atténue ces deux problèmes de performances en utilisant le paramètre object_hook
de json.load
et json.loads
. De les docs :
object_hook
est une fonction facultative appelée avec le résultat de tout objet littéral décodé (unedict
). La valeur de retour de object_hook sera utilisée à la place dedict
. Cette fonctionnalité peut être utilisée pour implémenter des décodeurs personnalisés
Étant donné que les dictionnaires qui ont imbriqué de nombreux niveaux au sein d’autres dictionnaires sont passés à object_hook
au fur et à mesure qu’ils sont décodés, nous pouvons spécifier toutes les chaînes ou listes qu’ils contiennent à ce moment-là et éviter d’avoir besoin d’une récursion profonde plus tard.
La réponse de Mark ne peut pas être utilisée en tant que object_hook
telle quelle, car elle rentre dans des dictionnaires imbriqués. Nous évitons cette récursion dans cette réponse avec le paramètre ignore_dicts
à _byteify
, qui lui est toujours transmis sauf lorsque object_hook
lui transmet une nouvelle variable dict
à octroyer. L'indicateur ignore_dicts
indique à _byteify
d'ignorer les dict
s puisqu'elles ont déjà été octroyées.
Enfin, nos implémentations de json_load_byteified
et json_loads_byteified
appellent _byteify
(avec ignore_dicts=True
) sur le résultat renvoyé par json.load
ou json.loads
pour traiter le cas où le texte JSON en cours de décodage ne comporte pas de dict
au niveau supérieur.
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 forme de chaînes de type str
au lieu de type unicode
. Parce que JSON est un sous-ensemble de YAML, cela 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']
Quelques points à noter cependant:
Je reçois objets de chaîne car toutes mes entrées sont ASCII codées. Si j'utilisais des entrées codées unicode, je les récupérerais sous forme d'objets unicode - il n'y a pas de conversion!
Vous devriez (probablement toujours) utiliser la fonction safe_load
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 fonction load
.
Si vous voulez un analyseur YAML qui supporte davantage la version 1.2 de la spécification (et analyse correctement les nombres très faibles ), essayez Ruamel YAML : pip install ruamel.yaml
et import ruamel.yaml as yaml
était tout ce dont j'avais besoin dans mon tests.
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), il vaut mieux utiliser une fonction 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 en tant que object_hook
, car cela pourrait vous permettre d'améliorer les performances des gros fichiers. Voir le réponse de Mirec Miskuf légèrement plus impliqué pour cela.
Il n’existe pas d’option intégrée permettant aux fonctions du module JSON de renvoyer des chaînes d’octets au lieu de chaînes Unicode. Cependant, cette fonction récursive simple et courte convertira tout objet JSON décodé de l'utilisation de chaînes unicode en chaînes d'octets codé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 ceci sur la sortie que vous obtenez à partir d'un appel json.load
ou json.loads
.
Quelques notes:
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 la compréhension du dictionnaire n’était prise en charge que jusqu’à Python 2.7.object_hook
ou object_pairs_hook
. La réponse de Mirec Miskuf est jusqu'à présent la seule à réussir à tirer cela correctement, bien qu'en conséquence, c'est beaucoup plus compliqué que mon approche.Vous pouvez utiliser le paramètre object_hook
pour json.loads
pour transmettre un convertisseur. Vous n'êtes pas obligé de faire la conversion après coup. Le module json
ne transmettra toujours que les codes object_hook
et il passera de manière récursive dans les dictons imbriqués, de sorte que vous n’aurez pas à récurer vous-même dans les dict 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 supposé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 objet unicode
. Vous devez utiliser value.encode(encoding)
avec un encodage valide, en fonction de ce que votre librairie externe attend.
Donc, 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)
En effet, 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 objets unicode
(c'est-à-dire que les chaînes JSON (javascript) peuvent stocker tout type de caractères unicode), il est donc logique de créer unicode
objets lors de la traduction de chaînes à partir de JSON. Les chaînes de caractères ordinaires ne correspondraient tout simplement pas, car la bibliothèque devrait deviner le codage souhaité.
Il est préférable d'utiliser des objets chaîne unicode
string partout. Votre meilleure option consiste donc à 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 avec 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']
Il existe un moyen facile de contourner le problème.
TL; DR - Utilisez ast.literal_eval()
au lieu de json.loads()
. ast
et json
sont tous deux dans la bibliothèque standard.
Bien que ce ne soit pas une réponse «parfaite», il en va assez bien si vous envisagez 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 poilu lorsque certains objets sont vraiment des chaînes Unicode. La réponse complète devient rapidement poilue.
La réponse de Mike Brennan est proche, mais il n'y a aucune raison de re-traverser la structure entière. Si vous utilisez le paramètre object_hook_pairs
(Python 2.7+):
object_pairs_hook
est une fonction facultative appelée avec le résultat de tout littéral d'objet décodé avec une liste ordonnée de paires. La valeur de retourobject_pairs_hook
sera utilisée à la place dedict
. Cette fonctionnalité peut être utilisée pour implémenter des décodeurs personnalisés qui reposent sur l'ordre dans lequel les paires de clé et de valeur sont décodées (par exemple,collections.OrderedDict
mémorisera l'ordre d'insertion). Siobject_hook
est également défini, leobject_pairs_hook
est prioritaire.
Avec cela, vous obtenez chaque objet JSON qui vous est remis, vous pouvez donc effectuer 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 utilisez le object_pairs_hook
. Vous devez vous préoccuper des listes, mais comme vous pouvez le constater, un objet de la liste sera correctement converti et vous n'avez pas besoin de recurse pour le réaliser.
EDIT: Un collègue a fait remarquer que Python2.6 n’a pas object_hook_pairs
. Vous pouvez toujours utiliser cette volonté Python2.6 en apportant une très petite modification. Dans le crochet ci-dessus, changez:
for key, value in pairs:
à
for key, value in pairs.iteritems():
Ensuite, utilisez object_hook
au 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_hook
entraîne l'instanciation d'un dictionnaire de moins pour chaque objet de l'objet JSON, ce qui, si vous analysez un document volumineux, peut valoir la peine.
Je crains qu'il n'y ait aucun moyen de réaliser cela 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 plus de rapidité) ou py_scanstring
si la version C n'est pas disponible. La fonction scanstring
est appelée plusieurs fois par presque chaque routine de simplejson pour décoder une structure pouvant contenir du texte. Vous devez soit synchroniser la valeur scanstring
dans simplejson.decoder, soit la sous-classe JSONDecoder
et fournir à peu près votre propre implémentation 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'implémentation scanstring
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).
Si vous avez une vieille bibliothèque qui a besoin d'une str
, je vous recommande de rechercher laborieusement la structure de données imbriquée après l'analyse (ce que je reconnais être ce que vous avez dit explicitement que vous vouliez éviter ... désolé), ou bien envelopper vos bibliothèques de façade où vous pouvez masser les paramètres d’entrée à un niveau plus granulaire. La deuxième approche pourrait être plus facile à gérer que la première si vos structures de données sont réellement imbriquées.
Comme Mark (Amery) le remarque correctement: L’utilisation du désérialiseur de PyYaml sur un cliché Json ne fonctionne que si vous avez ASCII uniquement. Au moins hors de la boîte.
Deux commentaires rapides sur l'approche PyYaml:
NEVER utilise yaml.load sur les données du terrain. C'est une fonctionnalité (!) De yaml pour exécuter du code arbitraire caché dans la structure.
Vous pouvez faites le fonctionner aussi pour les 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 les performances sont sans comparaison avec la réponse de Mark Amery:
En jetant des exemples de dict profondément imbriqués sur les deux méthodes, je comprends 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 l’encodage complet de l’arborescence et, se situe bien dans l’ordre de grandeur de l’implémentation basée sur C de json. Je trouve cela remarquablement rapide et sa plus robuste que la charge yaml sur des structures profondément imbriquées. Et moins d'erreurs de sécurité, regardant yaml.load.
=> Bien que j'apprécie un pointeur sur 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 les entrées de l'utilisateur. Dans ce cas, vous devrez probablement passer de toute façon sur votre structure, indépendamment des structures de données internes souhaitées (chaînes 'sandwich unicode' ou chaînes d'octets uniquement).
Pourquoi?
Unicode normalisation. Pour les ignorants: Prenez un antidouleur et lisez ceci .
Donc, en utilisant la récursion byteify, vous faites d'une pierre deux coups:
Lors de mes tests, il s'est avéré que le remplacement du input.encode ('utf-8') par un unododata.normalize ('NFC', entrée) .encode ('utf-8') était encore plus rapide que w/o NFC - mais je suppose qu’il dépend fortement des données de l’échantillon.
La vérité est que simplejson
et json
sont deux modules différents, au moins de la manière dont ils traitent avec unicode. Vous avez json
dans py 2.6+, ce qui vous donne des valeurs unicode, alors que simplejson
renvoie des objets chaîne. Essayez simplement easy_install-ing simplejson dans votre environnement et voyez si cela fonctionne. Ça l'a fait pour moi.
Utilisez juste pickle au lieu de json pour dump et load, 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 gérés correctement):
{u'field2': 2, u'field1': u'value1'}
{'field2': 2, 'field1': 'value1'}
Soutenez Python2 et 3 à l’aide de hook (from 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)
Résultats:
{'three': '', 'key': 'value', 'one': 'two'}
Donc, j'ai rencontré le même problème. Devinez quel était le premier résultat de Google.
Parce que j'ai besoin de transmettre toutes les données à PyGTK, les chaînes unicode ne me sont pas non plus très utiles. J'ai donc une autre méthode de conversion récursive. C'est en fait également nécessaire pour la conversion JSON de typesafe - json.dump () s'arrêterait sur tous les non-littéraux, comme les objets Python. Ne convertit pas les index dict cependant.
# 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
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
C'est tard pour le jeu, mais j'ai construit ce lanceur de sorts 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 juste 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.
J'ai eu un dict JSON comme une 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 fonction byteify
suggérée ci-dessus en convertissant la chaîne en un objet dict
à l'aide de ast.literal_eval(myStringDict)
.
Découvrez ceci répondez à 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 réellement la chaîne, elle n'apparaît pas dans vos données. Ne soyez pas projeté par la sortie imprimée.
Par exemple, essayez ceci:
print mail_accounts[0]["i"]
Vous ne verrez pas un u.
Avec Python 3.6, je rencontre encore parfois ce problème. Par exemple, lorsque je reçois une réponse d'une API REST et que je charge le texte de la réponse au format JSON, je reçois toujours les chaînes unicode . Nous avons trouvé une solution simple à l'aide de json.dumps ().
response_message = json.loads(json.dumps(response.text))
print(response_message)
voici un encodeur récursif écrit en C: https://github.com/axiros/nested_encode
Surcharge de performances 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)