คำตอบของฉันพูดถึงกรณีที่เฉพาะเจาะจง (และค่อนข้างธรรมดา) ซึ่งคุณไม่จำเป็นต้องแปลง xml ทั้งหมดเป็น json แต่สิ่งที่คุณต้องใช้คือสำรวจ / เข้าถึงส่วนเฉพาะของ xml และคุณต้องการให้รวดเร็วและง่าย (โดยใช้การดำเนินการแบบ json / dict)
เข้าใกล้
สำหรับสิ่งนี้สิ่งสำคัญคือต้องทราบว่าการแยกวิเคราะห์ xml เพื่อ etree โดยใช้lxml
นั้นเร็วมาก ส่วนที่ช้าในคำตอบอื่น ๆ ส่วนใหญ่คือการผ่านครั้งที่สอง: การข้ามผ่านโครงสร้าง 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 เท่านั้นวิธีการนี้ให้ความเร็วที่น่าทึ่งและโดดเด่นด้วยอัตรา 70 (!)เมื่อเทียบกับการใช้ xmltodict ของ @Martin Blech จากนั้นท่องไปตามคำสั่งโดยตรง
โบนัส
เป็นโบนัสเนื่องจากโครงสร้างของเราคล้าย dict อยู่แล้วเราจึงได้รับการติดตั้งใหม่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 ที่ถูกต้อง
ฉันไม่ได้เปรียบเทียบส่วนนี้