私の答えは、xml全体をjsonに変換する必要がない特定の(そしてやや一般的な)ケースに対処しますが、必要なのはxmlの特定の部分をトラバース/アクセスすることであり、高速である必要があります。シンプル(json / dictのような操作を使用)。
アプローチ
このため、xml を使用して etree に解析するのlxml
は非常に高速であることに注意することが重要です。他のほとんどの回答で遅い部分は 2 番目のパスです。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 の特定の要素のみを処理する必要がある私の特定のユースケースでは、このアプローチは、@Martin Blech のxmltodictを使用してからdict を直接トラバースする場合と比較して、70 倍 (!) の驚くべき驚くべきスピードアップをもたらしました。
ボーナス
おまけとして、私たちの構造はすでに dict に似ているので、別の代替実装xml2json
を無料で入手できます。dict のような構造を に渡すだけですjson.dumps
。何かのようなもの:
def xml_to_json(xmlstr, **kwargs):
x = xml_to_dictlike(xmlstr, **kwargs)
return json.dumps(x)
attr_prefix
XML に属性が含まれている場合は、キーが有効な json キーであることを確認するために、英数字 (「ATTR_」など) を使用する必要があります。
この部分のベンチマークは行っていません。