Мой ответ касается конкретного (и несколько распространенного) случая, когда вам на самом деле не нужно конвертировать весь XML в JSON, но вам нужно перебрать / получить доступ к определенным частям XML, и вам нужно, чтобы он был быстрым , и просто (используя json / dict-подобные операции).
Подходить
Для этого важно отметить, что синтаксический анализ xml с использованием etree lxml
выполняется очень быстро. Медленная часть в большинстве других ответов - это второй проход: обход структуры etree (обычно в python-land), преобразование ее в json.
Что приводит меня к подходу, который я нашел наилучшим для этого случая: анализ xml с использованием lxml
, а затем обертывание узлов etree (лениво), предоставляя им dict-подобный интерфейс.
Код
Вот код:
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),
)
Эта реализация не завершена, например, она не поддерживает чисто случаи, когда элемент имеет как текст, так и атрибуты, или как текст, так и дочерние элементы (только потому, что он мне не нужен, когда я его пишу ...) Это должно быть легко чтобы улучшить его, хотя.
скорость
В моем конкретном случае использования, когда мне нужно было обрабатывать только определенные элементы xml, этот подход дал удивительное и поразительное ускорение в 70 (!) Раз по сравнению с использованием xmltodict @Martin Blech и последующим обходом диктата напрямую.
бонус
В качестве бонуса, поскольку наша структура уже похожа на диктовку, мы получаем другую альтернативную реализацию xml2json
бесплатно. Нам просто нужно передать нашу подобную диктату структуру json.dumps
. Что-то вроде:
def xml_to_json(xmlstr, **kwargs):
x = xml_to_dictlike(xmlstr, **kwargs)
return json.dumps(x)
Если ваш xml содержит атрибуты, вам нужно использовать буквенно-цифровые attr_prefix
символы (например, «ATTR_»), чтобы ключи были действительными ключами json.
Я не тестировал эту часть.