Моя відповідь стосується конкретного (і дещо поширеного) випадку, коли вам не потрібно конвертувати весь xml в json, але вам потрібно перейти / отримати доступ до конкретних частин xml, і вам потрібно, щоб він був швидким , і простий (з використанням json / dict-подібних операцій).
Підхід
Для цього важливо відзначити, що синтаксичний аналіз xml до etree використовується lxml
дуже швидко. Повільна частина більшості інших відповідей - це другий перехід: проходження структури етрі (зазвичай в python-land), перетворення її в json.
Що призводить мене до підходу, який я знайшов найкращим для цього випадку: розбору xml за допомогою lxml
, а потім загортання вузлів etree (ліниво), надання їм інтерфейсу, що нагадує дікт.
Код
Ось код:
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.
Я не оцінював цю частину.