Ma réponse aborde le cas spécifique (et quelque peu courant) où vous n'avez pas vraiment besoin de convertir le xml entier en json, mais ce dont vous avez besoin est de parcourir / accéder à des parties spécifiques du xml, et vous en avez besoin pour être rapide , et simple (en utilisant des opérations de type json / dict).
Approche
Pour cela, il est important de noter que l'analyse d'un xml vers etree en utilisant lxml
est super rapide. La partie lente dans la plupart des autres réponses est la deuxième passe: traverser la structure etree (généralement en python-land), la convertir en json.
Ce qui m'amène à l'approche que j'ai trouvée la meilleure pour ce cas: analyser le XML en utilisant lxml
, puis envelopper les nœuds etree (paresseusement), en leur fournissant une interface de type dict.
Code
Voici le code:
from collections import Mapping
import lxml.etree
class ETreeDictWrapper(Mapping):
def __init__(self, elem, attr_prefix = '@', list_tags = ()):
self.elem = elem
self.attr_prefix = attr_prefix
self.list_tags = list_tags
def _wrap(self, e):
if isinstance(e, basestring):
return e
if len(e) == 0 and len(e.attrib) == 0:
return e.text
return type(self)(
e,
attr_prefix = self.attr_prefix,
list_tags = self.list_tags,
)
def __getitem__(self, key):
if key.startswith(self.attr_prefix):
return self.elem.attrib[key[len(self.attr_prefix):]]
else:
subelems = [ e for e in self.elem.iterchildren() if e.tag == key ]
if len(subelems) > 1 or key in self.list_tags:
return [ self._wrap(x) for x in subelems ]
elif len(subelems) == 1:
return self._wrap(subelems[0])
else:
raise KeyError(key)
def __iter__(self):
return iter(set( k.tag for k in self.elem) |
set( self.attr_prefix + k for k in self.elem.attrib ))
def __len__(self):
return len(self.elem) + len(self.elem.attrib)
# defining __contains__ is not necessary, but improves speed
def __contains__(self, key):
if key.startswith(self.attr_prefix):
return key[len(self.attr_prefix):] in self.elem.attrib
else:
return any( e.tag == key for e in self.elem.iterchildren() )
def xml_to_dictlike(xmlstr, attr_prefix = '@', list_tags = ()):
t = lxml.etree.fromstring(xmlstr)
return ETreeDictWrapper(
t,
attr_prefix = '@',
list_tags = set(list_tags),
)
Cette implémentation n'est pas complète, par exemple, elle ne prend pas en charge proprement les cas où un élément a à la fois du texte et des attributs, ou à la fois du texte et des enfants (uniquement parce que je n'en avais pas besoin lorsque je l'ai écrit ...) Cela devrait être facile pour l'améliorer, cependant.
La vitesse
Dans mon cas d'utilisation spécifique, où je devais seulement des éléments spécifiques du processus du xml, cette approche a donné une surprenante et speedup frappe par un facteur de 70 (!) Par rapport à l' utilisation de @ Martin Blech de xmltodict puis traversant le dict directement.
Prime
En prime, comme notre structure est déjà de type dict, nous obtenons une autre implémentation alternative de xml2json
gratuitement. Nous avons juste besoin de passer notre structure de type dict à json.dumps
. Quelque chose comme:
def xml_to_json(xmlstr, **kwargs):
x = xml_to_dictlike(xmlstr, **kwargs)
return json.dumps(x)
Si votre xml inclut des attributs, vous devez utiliser des caractères alphanumériques attr_prefix
(par exemple "ATTR_"), pour vous assurer que les clés sont des clés json valides.
Je n'ai pas évalué cette partie.