我的答案针对的是特定情况(并且在某种程度上很常见),在这种情况下,您实际上并不需要将整个xml转换为json,但是您需要遍历/访问xml的特定部分,并且需要快速进行处理,并且简单(使用类似于json / dict的操作)。
方法
为此,需要注意的是,将xml解析为etree lxml
超级快。其他大多数答案中最慢的部分是第二遍:遍历etree结构(通常在python-land中),将其转换为json。
这使我lxml
想到了最适合这种情况的方法:使用解析xml ,然后(延迟地)包装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的特定元素,与使用@Martin Blech的xmltodict然后直接遍历该dict 相比,此方法可提供惊人的惊人的加速70(!)。
奖金
另外,由于我们的结构已经像字典一样,我们可以xml2json
免费获得另一种替代实现。我们只需要将类似于dict的结构传递给即可json.dumps
。就像是:
def xml_to_json(xmlstr, **kwargs):
x = xml_to_dictlike(xmlstr, **kwargs)
return json.dumps(x)
如果您的xml包含属性,则需要使用一些字母数字attr_prefix
(例如“ ATTR_”),以确保键是有效的json键。
我还没有对这部分进行基准测试。