使用Python将XML转换为JSON?

我在网上看到了一些不雅的XML-> JSON代码,并且与Stack的用户进行了一些交互,我相信这个人群可以帮助Googlesearch结果的前几页。

因此,我们正在parsing天气信息,我们需要在多个网站上填充天气小部件。 我们正在研究基于Python的解决scheme。

这个公共weather.com RSS提要是我们将parsing的一个很好的例子( 我们的实际的weather.com提要包含了更多的信息,因为它们有合作关系 )。

简而言之,我们应该如何使用Python将XML转换为JSON?

在XML和JSON之间没有“一对一”的映射关系,所以将一个转换为另一个必然需要对你想要对结果什么的一些理解。

也就是说,Python的标准库有几个parsingXML的模块 (包括DOM,SAX和ElementTree)。 从Python 2.6开始,支持将json模块中的Python数据结构转换为JSON。

所以基础设施就在那里。

xmltodict (完全公开:我写了它)可以帮助你转换你的XML到一个字典+列表+string结构,遵循这个“标准” 。 它是基于Expat的,因此速度非常快,不需要在内存中加载整个XML树。

一旦你有了这个数据结构,你可以将它序列化为JSON:

 import xmltodict, json o = xmltodict.parse('<e> <a>text</a> <a>text</a> </e>') json.dumps(o) # '{"e": {"a": ["text", "text"]}}' 

您可以使用xmljson库来使用不同的XML JSON约定进行转换。

例如,这个XML:

 <p id="1">text</p> 

通过BadgerFish约定转换为:

 { 'p': { '@id': 1, '$': 'text' } } 

并通过GData惯例进入(不支持属性):

 { 'p': { '$t': 'text' } } 

…并通过派克公约进入此(属性不支持):

 { 'p': 'text' } 

使用相同的约定,可以将XML从XML转换为JSON,从JSON转换为XML:

 >>> import json, xmljson >>> from lxml.etree import fromstring, tostring >>> xml = fromstring('<p id="1">text</p>') >>> json.dumps(xmljson.badgerfish.data(xml)) '{"p": {"@id": 1, "$": "text"}}' >>> xmljson.parker.etree({'ul': {'li': [1, 2]}}) # Creates [<ul><li>1</li><li>2</li></ul>] 

披露:我写了这个图书馆。 希望它有助于未来的search者。

这是我为此编写的代码。 没有parsing的内容,只是简单的转换。

 from xml.dom import minidom import simplejson as json def parse_element(element): dict_data = dict() if element.nodeType == element.TEXT_NODE: dict_data['data'] = element.data if element.nodeType not in [element.TEXT_NODE, element.DOCUMENT_NODE, element.DOCUMENT_TYPE_NODE]: for item in element.attributes.items(): dict_data[item[0]] = item[1] if element.nodeType not in [element.TEXT_NODE, element.DOCUMENT_TYPE_NODE]: for child in element.childNodes: child_name, child_dict = parse_element(child) if child_name in dict_data: try: dict_data[child_name].append(child_dict) except AttributeError: dict_data[child_name] = [dict_data[child_name], child_dict] else: dict_data[child_name] = child_dict return element.nodeName, dict_data if __name__ == '__main__': dom = minidom.parse('data.xml') f = open('data.json', 'w') f.write(json.dumps(parse_element(dom), sort_keys=True, indent=4)) f.close() 

你可能想看看http://designtheory.org/library/extrep/designdb-1.0.pdf 。 这个项目从一个大型的XML文件库的XML到JSON转换开始。 在转换过程中做了很多研究,最简单直观的XML – > JSON映射被生成了(在文档的早期描述)。 总之,将所有内容转换为JSON对象,并将重复的块作为对象列表。

对象表示键/值对(Python中的字典,Java中的hashmap,JavaScript中的对象)

没有映射到XML来获取相同的文档,原因是,不知道键/值对是属性还是<key>value</key> ,因此信息丢失。

如果你问我,属性是一个黑客入门; 然后他们再次为HTML工作。

有一种方法可以将基于XML的标记传输为JSON,这样可以将其无损地转换回原始forms。 请参阅http://jsonml.org/

这是一种JSON的XSLT。 我希望你觉得它有帮助

那么,可能最简单的方法就是将XMLparsing为字典,然后用simplejson将其序列化。

虽然内置的XMLparsing库是相当不错的,我偏爱lxml 。

但是为了parsingRSS提要,我推荐Universal Feed Parser ,它也可以parsingAtom。 它的主要优点是它可以消化甚至是大多数畸形的饲料。

Python 2.6已经包含了一个JSONparsing器,但是速度更快的新版本可以用作simplejson 。

使用这些工具构build您的应用程序不应该那么困难。

我build议不要直接转换。 将XML转换为对象,然后将对象转换为JSON。

在我看来,这给出了XML和JSON如何对应的更清晰的定义。

这需要时间来正确的,你甚至可以编写工具来帮助你生成一些,但它看起来大致是这样的:

 class Channel: def __init__(self) self.items = [] self.title = "" def from_xml( self, xml_node ): self.title = xml_node.xpath("title/text()")[0] for x in xml_node.xpath("item"): item = Item() item.from_xml( x ) self.items.append( item ) def to_json( self ): retval = {} retval['title'] = title retval['items'] = [] for x in items: retval.append( x.to_json() ) return retval class Item: def __init__(self): ... def from_xml( self, xml_node ): ... def to_json( self ): ... 

当我在python中使用XML时,我几乎总是使用lxml包。 我怀疑大多数人使用lxml。 你可以使用xmltodict,但你将不得不再次parsingXML的惩罚。

使用lxml将XML转换为json:

  1. 用lxmlparsingXML文档
  2. 将lxml转换为字典
  3. 将列表转换为json

我在我的项目中使用以下类。 使用toJson方法。

 from lxml import etree import json class Element: ''' Wrapper on the etree.Element class. Extends functionality to output element as a dictionary. ''' def __init__(self, element): ''' :param: element a normal etree.Element instance ''' self.element = element def toDict(self): ''' Returns the element as a dictionary. This includes all child elements. ''' rval = { self.element.tag: { 'attributes': dict(self.element.items()), }, } for child in self.element: rval[self.element.tag].update(Element(child).toDict()) return rval class XmlDocument: ''' Wraps lxml to provide: - cleaner access to some common lxml.etree functions - converter from XML to dict - converter from XML to json ''' def __init__(self, xml = '<empty/>', filename=None): ''' There are two ways to initialize the XmlDocument contents: - String - File You don't have to initialize the XmlDocument during instantiation though. You can do it later with the 'set' method. If you choose to initialize later XmlDocument will be initialized with "<empty/>". :param: xml Set this argument if you want to parse from a string. :param: filename Set this argument if you want to parse from a file. ''' self.set(xml, filename) def set(self, xml=None, filename=None): ''' Use this to set or reset the contents of the XmlDocument. :param: xml Set this argument if you want to parse from a string. :param: filename Set this argument if you want to parse from a file. ''' if filename is not None: self.tree = etree.parse(filename) self.root = self.tree.getroot() else: self.root = etree.fromstring(xml) self.tree = etree.ElementTree(self.root) def dump(self): etree.dump(self.root) def getXml(self): ''' return document as a string ''' return etree.tostring(self.root) def xpath(self, xpath): ''' Return elements that match the given xpath. :param: xpath ''' return self.tree.xpath(xpath); def nodes(self): ''' Return all elements ''' return self.root.iter('*') def toDict(self): ''' Convert to a python dictionary ''' return Element(self.root).toDict() def toJson(self, indent=None): ''' Convert to JSON ''' return json.dumps(self.toDict(), indent=indent) if __name__ == "__main__": xml='''<system> <product> <demod> <frequency value='2.215' units='MHz'> <blah value='1'/> </frequency> </demod> </product> </system> ''' doc = XmlDocument(xml) print doc.toJson(indent=4) 

内置main的输出是:

 { "system": { "attributes": {}, "product": { "attributes": {}, "demod": { "attributes": {}, "frequency": { "attributes": { "units": "MHz", "value": "2.215" }, "blah": { "attributes": { "value": "1" } } } } } } } 

这是一个这个XML的转换:

 <system> <product> <demod> <frequency value='2.215' units='MHz'> <blah value='1'/> </frequency> </demod> </product> </system> 

jsonpickle或者如果您使用feedparser,您可以尝试feed_pa​​rser_to_json.py

我发现,对于简单的XML剪辑,使用正则expression式会节省麻烦。 例如:

 # <user><name>Happy Man</name>...</user> import re names = re.findall(r'<name>(\w+)<\/name>', xml_string) # do some thing to names 

要做到这一点,就像@Dan说的那样通过XMLparsing来实现,因为数据是不同的,所以并不是一劳永逸的解决scheme。 我的build议是使用lxml。 虽然没有完成json, lxml.objectify给了安静的好结果:

 >>> from lxml import objectify >>> root = objectify.fromstring(""" ... <root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> ... <a attr1="foo" attr2="bar">1</a> ... <a>1.2</a> ... <b>1</b> ... <b>true</b> ... <c>what?</c> ... <d xsi:nil="true"/> ... </root> ... """) >>> print(str(root)) root = None [ObjectifiedElement] a = 1 [IntElement] * attr1 = 'foo' * attr2 = 'bar' a = 1.2 [FloatElement] b = 1 [IntElement] b = True [BoolElement] c = 'what?' [StringElement] d = None [NoneElement] * xsi:nil = 'true' 

我的答案解决了特定的(有点常见)的情况,你并不需要将整个xml转换成json,但是你需要的是遍历/访问xml的特定部分,而且你需要快速的 简单 (使用类似json / dict的操作)。

途径

为此,需要注意的是使用lxmlparsingxml到etree是非常快的。 大部分其他答案的缓慢部分是第二遍:穿越etree结构(通常在python地),将其转换成json。

这导致我find了最适合这种情况的方法:使用lxmlparsingxml,然后包装etree节点(懒惰地),为它们提供类似于字典的界面。

代码如下:

 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相比,这种方法提供了令人惊讶惊人的加速,并且直接遍历字典。

奖金

作为奖励,由于我们的结构已经是字典式的,所以我们可以免费获得另一个xml2json实现。 我们只需要将我们的字典结构传递给json.dumps 。 就像是:

 def xml_to_json(xmlstr, **kwargs): x = xml_to_dictlike(xmlstr, **kwargs) return json.dumps(x) 

如果你的XML包含属性,你需要使用一些字母数字attr_prefix (例如“ATTR_”),以确保键是有效的JSON键。

我没有对这部分进行基准testing。

这里的东西是积极的维护,到目前为止我最喜欢的是: python中的xml2json

任何人可能仍然需要这个。 这是一个更新,简单的代码来做这个转换。

 from xml.etree import ElementTree as ET xml = ET.parse('FILE_NAME.xml') parsed = parseXmlToJson(xml) def parseXmlToJson(xml): response = {} for child in list(xml): if len(list(child)) > 0: response[child.tag] = parseXmlToJson(child) else: response[child.tag] = child.text or '' # one-liner equivalent # response[child.tag] = parseXmlToJson(child) if len(list(child)) > 0 else child.text or '' return response