Jawaban saya membahas kasus spesifik (dan agak umum) di mana Anda tidak benar-benar perlu mengubah seluruh xml menjadi json, tetapi yang Anda butuhkan adalah untuk melintasi / mengakses bagian-bagian tertentu dari xml, dan Anda perlu cepat , dan sederhana (menggunakan operasi json / dict-like).
Pendekatan
Untuk ini, penting untuk dicatat bahwa parsing xml untuk menggunakan etree lxml
sangat cepat. Bagian lambat di sebagian besar jawaban lain adalah lintasan kedua: melintasi struktur etree (biasanya di python-land), mengubahnya menjadi json.
Yang membawa saya ke pendekatan yang saya temukan terbaik untuk kasus ini: parsing menggunakan xml lxml
, dan kemudian membungkus node etree (malas), menyediakan mereka dengan antarmuka seperti dict.
Kode
Berikut kodenya:
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),
)
Implementasi ini tidak lengkap, misalnya, tidak mendukung kasus di mana sebuah elemen memiliki teks dan atribut, atau teks dan anak-anak (hanya karena saya tidak membutuhkannya ketika saya menulisnya ...) Itu harus mudah untuk memperbaikinya.
Mempercepat
Dalam kasus penggunaan khusus saya, di mana saya hanya perlu memproses elemen-elemen spesifik xml, pendekatan ini memberikan speedup mengejutkan dan mencolok dengan faktor 70 (!) Dibandingkan dengan menggunakan xmltodict @Martin Blech dan kemudian menelusuri dict secara langsung.
Bonus
Sebagai bonus, karena struktur kami sudah seperti dict, kami mendapatkan implementasi alternatif lain xml2json
secara gratis. Kita hanya perlu meneruskan struktur seperti dict kita json.dumps
. Sesuatu seperti:
def xml_to_json(xmlstr, **kwargs):
x = xml_to_dictlike(xmlstr, **kwargs)
return json.dumps(x)
Jika xml Anda menyertakan atribut, Anda harus menggunakan beberapa alfanumerik attr_prefix
(mis. "ATTR_"), untuk memastikan kunci tersebut adalah kunci json yang valid.
Saya belum membandingkan bagian ini.