Địa chỉ câu trả lời của tôi cụ thể (và có phần chung) Trường hợp bạn không thực sự cần phải chuyển đổi toàn bộ xml để json, nhưng những gì bạn cần là traverse / truy cập phần cụ thể của xml, và bạn cần nó để được nhanh chóng , và đơn giản (sử dụng các thao tác json / dict-like).
Tiếp cận
Đối với điều này, điều quan trọng cần lưu ý là phân tích cú pháp xml sang etree bằng cách sử dụng lxml
là siêu nhanh. Phần chậm trong hầu hết các câu trả lời khác là thông qua thứ hai: đi qua cấu trúc etree (thường ở vùng đất trăn), chuyển đổi nó thành json.
Điều này dẫn tôi đến cách tiếp cận mà tôi thấy tốt nhất trong trường hợp này: phân tích xml bằng cách sử dụng lxml
, sau đó gói các nút etree (một cách lười biếng), cung cấp cho chúng một giao diện giống như chính tả.
Mã
Đây là mã:
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),
)
Việc triển khai này chưa hoàn tất, ví dụ, nó không hỗ trợ rõ ràng các trường hợp trong đó một phần tử có cả văn bản và thuộc tính hoặc cả văn bản và trẻ em (chỉ vì tôi không cần nó khi tôi viết nó ...) Thật dễ dàng để cải thiện nó, mặc dù.
Tốc độ
Trong trường hợp cụ thể sử dụng của tôi, nơi tôi cần đến các yếu tố quy trình cụ thể duy nhất của xml, phương pháp này đã đưa ra một đáng ngạc nhiên và tăng tốc ấn tượng bởi một yếu tố của 70 (!) So với sử dụng @ Martin Blech của xmltodict và sau đó đi qua các dict trực tiếp.
Tặng kem
Như một phần thưởng, vì cấu trúc của chúng tôi giống như chính tả, chúng tôi nhận được một triển khai thay thế khác xml2json
miễn phí. Chúng ta chỉ cần chuyển cấu trúc giống như chính tả của mình json.dumps
. Cái gì đó như:
def xml_to_json(xmlstr, **kwargs):
x = xml_to_dictlike(xmlstr, **kwargs)
return json.dumps(x)
Nếu xml của bạn bao gồm các thuộc tính, bạn cần sử dụng một số chữ và số attr_prefix
(ví dụ: "ATTR_"), để đảm bảo các khóa là các khóa json hợp lệ.
Tôi đã không điểm chuẩn phần này.