我在网上看到过相当多笨拙的XML->JSON代码,并与Stack的用户进行了一些互动,我相信这群人能比谷歌结果的前几页提供更多的帮助。

因此,我们正在解析一个天气提要,我们需要在许多网站上填充天气小部件。我们现在正在研究基于python的解决方案。

这个公共weather.com RSS提要是我们将要解析的内容的一个很好的例子(我们实际的weather.com提要包含额外的信息,因为与他们有合作关系)。

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


当前回答

有一种方法可以将基于xml的标记传输为JSON,允许它无损地转换回原始形式。见http://jsonml.org/。

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

其他回答

献给任何可能还需要这个的人。下面是一个更新的、简单的代码来进行这种转换。

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

可能最简单的方法是将XML解析为字典,然后用simplejson序列化它。

我的答案针对的是特定的(有点常见的)情况,在这种情况下,您实际上不需要将整个xml转换为json,但您需要遍历/访问xml的特定部分,并且您需要它是快速和简单的(使用json/dict-like操作)。

方法

为此,需要注意的是,使用lxml将xml解析为树非常快。大多数其他答案中最慢的部分是第二步:遍历树结构(通常在python领域),将其转换为json。

这使我采用了我认为最适合这种情况的方法:使用lxml解析xml,然后(惰性地)包装树节点,为它们提供一个类似字典的接口。

Code

代码如下:

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然后直接遍历字典相比,这种方法提供了惊人的70倍加速(!)。

奖金

作为奖励,由于我们的结构已经是dict-like的,我们可以免费获得xml2json的另一种替代实现。我们只需要将dict-like结构传递给json.dumps。喜欢的东西:

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

如果你的xml包含属性,你需要使用一些字母数字attr_prefix(例如。"ATTR_"),以确保这些键是有效的json键。

我还没有对这部分进行基准测试。

检查lxml2json(披露:我写的)

https://github.com/rparelius/lxml2json

它非常快速、轻量级(只需要lxml),一个优点是您可以控制某些元素是转换为列表还是字典

xmltodict(完全公开:是我写的)可以帮助您按照这个“标准”将XML转换为字典+列表+字符串结构。它是基于expat的,所以速度非常快,而且不需要在内存中加载整个XML树。

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

import xmltodict, json

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