202

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

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

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

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

4

20 回答 20

334

xmltodict(完全公开:我写的)可以帮助您将 XML 转换为 dict+list+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"]}}'
于 2012-04-18T01:06:05.353 回答
66

XML 和 JSON 之间没有“一对一”的映射关系,因此将一个映射到另一个必然需要了解您想要对结果什么。

话虽如此,Python 的标准库有几个用于解析 XML 的模块(包括 DOM、SAX 和 ElementTree)。json从 Python 2.6 开始,模块中包含对将 Python 数据结构与 JSON 相互转换的支持。

所以基础设施就在那里。

于 2008-10-10T14:34:55.580 回答
31

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

例如,这个 XML:

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

通过BadgerFish 约定转换为:

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

并通过GData 约定进入此(不支持属性):

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

...并通过Parker 约定进入此(不支持属性):

{
  'p': 'text'
}

可以使用相同的约定从 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>]

披露:我写了这个库。希望它对未来的搜索者有所帮助。

于 2015-09-20T07:37:54.507 回答
15

如果有时您只得到响应代码而不是所有数据,那么会出现像 json parse 这样的错误,因此您需要将其转换为文本

import xmltodict

data = requests.get(url)
xpars = xmltodict.parse(data.text)
json = json.dumps(xpars)
print json 
于 2018-05-12T08:17:18.023 回答
13

对于可能仍然需要这个的任何人。这是一个更新的、简单的代码来进行这种转换。

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
于 2017-11-02T17:25:44.287 回答
9

这是我为此构建的代码。没有内容解析,只是简单的转换。

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()
于 2012-04-19T15:35:21.540 回答
7

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

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

于 2011-06-10T02:48:15.637 回答
5

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

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

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

如果你问我,属性是一个技巧。然后他们再次为 HTML 工作得很好。

于 2010-10-07T18:50:24.333 回答
5

我建议不要直接转换。将 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 ):
    ...
于 2012-04-18T01:24:21.060 回答
4

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

于 2008-10-10T14:30:59.193 回答
4

当我在 python 中使用 XML 做任何事情时,我几乎总是使用 lxml 包。我怀疑大多数人使用lxml。您可以使用 xmltodict 但您将不得不支付再次解析 XML 的代价。

要使用 lxml 将 XML 转换为 json,请执行以下操作:

  1. 使用 lxml 解析 XML 文档
  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>
于 2017-05-09T16:30:31.230 回答
2

虽然用于 XML 解析的内置库非常好,但我偏爱lxml

但是对于解析 RSS 提要,我推荐Universal Feed Parser,它也可以解析 Atom。它的主要优点是它甚至可以消化大多数畸形的饲料。

Python 2.6 已经包含一个 JSON 解析器,但是一个具有更高速度的新版本作为simplejson提供。

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

于 2008-10-10T18:51:42.380 回答
2

我发现对于简单的 XML 剪辑,使用正则表达式会省事。例如:

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

正如@Dan 所说,通过 XML 解析来做到这一点,没有万能的解决方案,因为数据不同。我的建议是使用 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'
于 2012-04-04T13:06:42.230 回答
2

我的回答解决了您实际上并不需要将整个 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 的特定元素,与使用 @Martin Blech 的xmltodict然后直接遍历 dict相比,这种方法提供了惊人的 70 倍(!)的加速。

奖金

作为奖励,由于我们的结构已经类似于 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 键。

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

于 2016-10-20T10:38:21.313 回答
2

查看 lxml2json (披露:我写的)

https://github.com/rparelius/lxml2json

它非常快速、轻量级(只需要 lxml),并且一个优点是您可以控制是否将某些元素转换为列表或 dicts

于 2018-09-12T01:21:51.377 回答
1

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

于 2010-12-09T06:27:59.050 回答
1

您可以使用 declxml。它具有多属性和复杂的嵌套支持等高级功能。你只需要为它写一个简单的处理器。同样使用相同的代码,您也可以转换回 JSON。它相当简单,文档很棒。

链接:https ://declxml.readthedocs.io/en/latest/index.html

于 2018-11-30T04:52:07.953 回答
1

如果您不想使用任何外部库和 3rd 方工具,请尝试以下代码。

代码

import re
import json

def getdict(content):
    res=re.findall("<(?P<var>\S*)(?P<attr>[^/>]*)(?:(?:>(?P<val>.*?)</(?P=var)>)|(?:/>))",content)
    if len(res)>=1:
        attreg="(?P<avr>\S+?)(?:(?:=(?P<quote>['\"])(?P<avl>.*?)(?P=quote))|(?:=(?P<avl1>.*?)(?:\s|$))|(?P<avl2>[\s]+)|$)"
        if len(res)>1:
            return [{i[0]:[{"@attributes":[{j[0]:(j[2] or j[3] or j[4])} for j in re.findall(attreg,i[1].strip())]},{"$values":getdict(i[2])}]} for i in res]
        else:
            return {res[0]:[{"@attributes":[{j[0]:(j[2] or j[3] or j[4])} for j in re.findall(attreg,res[1].strip())]},{"$values":getdict(res[2])}]}
    else:
        return content

with open("test.xml","r") as f:
    print(json.dumps(getdict(f.read().replace('\n',''))))

样本输入

<details class="4b" count=1 boy>
    <name type="firstname">John</name>
    <age>13</age>
    <hobby>Coin collection</hobby>
    <hobby>Stamp collection</hobby>
    <address>
        <country>USA</country>
        <state>CA</state>
    </address>
</details>
<details empty="True"/>
<details/>
<details class="4a" count=2 girl>
    <name type="firstname">Samantha</name>
    <age>13</age>
    <hobby>Fishing</hobby>
    <hobby>Chess</hobby>
    <address current="no">
        <country>Australia</country>
        <state>NSW</state>
    </address>
</details>

输出

[
  {
    "details": [
      {
        "@attributes": [
          {
            "class": "4b"
          },
          {
            "count": "1"
          },
          {
            "boy": ""
          }
        ]
      },
      {
        "$values": [
          {
            "name": [
              {
                "@attributes": [
                  {
                    "type": "firstname"
                  }
                ]
              },
              {
                "$values": "John"
              }
            ]
          },
          {
            "age": [
              {
                "@attributes": []
              },
              {
                "$values": "13"
              }
            ]
          },
          {
            "hobby": [
              {
                "@attributes": []
              },
              {
                "$values": "Coin collection"
              }
            ]
          },
          {
            "hobby": [
              {
                "@attributes": []
              },
              {
                "$values": "Stamp collection"
              }
            ]
          },
          {
            "address": [
              {
                "@attributes": []
              },
              {
                "$values": [
                  {
                    "country": [
                      {
                        "@attributes": []
                      },
                      {
                        "$values": "USA"
                      }
                    ]
                  },
                  {
                    "state": [
                      {
                        "@attributes": []
                      },
                      {
                        "$values": "CA"
                      }
                    ]
                  }
                ]
              }
            ]
          }
        ]
      }
    ]
  },
  {
    "details": [
      {
        "@attributes": [
          {
            "empty": "True"
          }
        ]
      },
      {
        "$values": ""
      }
    ]
  },
  {
    "details": [
      {
        "@attributes": []
      },
      {
        "$values": ""
      }
    ]
  },
  {
    "details": [
      {
        "@attributes": [
          {
            "class": "4a"
          },
          {
            "count": "2"
          },
          {
            "girl": ""
          }
        ]
      },
      {
        "$values": [
          {
            "name": [
              {
                "@attributes": [
                  {
                    "type": "firstname"
                  }
                ]
              },
              {
                "$values": "Samantha"
              }
            ]
          },
          {
            "age": [
              {
                "@attributes": []
              },
              {
                "$values": "13"
              }
            ]
          },
          {
            "hobby": [
              {
                "@attributes": []
              },
              {
                "$values": "Fishing"
              }
            ]
          },
          {
            "hobby": [
              {
                "@attributes": []
              },
              {
                "$values": "Chess"
              }
            ]
          },
          {
            "address": [
              {
                "@attributes": [
                  {
                    "current": "no"
                  }
                ]
              },
              {
                "$values": [
                  {
                    "country": [
                      {
                        "@attributes": []
                      },
                      {
                        "$values": "Australia"
                      }
                    ]
                  },
                  {
                    "state": [
                      {
                        "@attributes": []
                      },
                      {
                        "$values": "NSW"
                      }
                    ]
                  }
                ]
              }
            ]
          }
        ]
      }
    ]
  }
]
于 2020-08-24T07:09:54.300 回答
1

不久前我在github上发表了一篇..

https://github.com/davlee1972/xml_to_json

此转换器是用 Python 编写的,会将一个或多个 XML 文件转换为 JSON / JSONL 文件

它需要一个 XSD 模式文件来确定嵌套的 json 结构(字典与列表)和 json 等效数据类型。

python xml_to_json.py -x PurchaseOrder.xsd PurchaseOrder.xml

INFO - 2018-03-20 11:10:24 - Parsing XML Files..
INFO - 2018-03-20 11:10:24 - Processing 1 files
INFO - 2018-03-20 11:10:24 - Parsing files in the following order:
INFO - 2018-03-20 11:10:24 - ['PurchaseOrder.xml']
DEBUG - 2018-03-20 11:10:24 - Generating schema from PurchaseOrder.xsd
DEBUG - 2018-03-20 11:10:24 - Parsing PurchaseOrder.xml
DEBUG - 2018-03-20 11:10:24 - Writing to file PurchaseOrder.json
DEBUG - 2018-03-20 11:10:24 - Completed PurchaseOrder.xml

我还有一个后续的 xml 到镶木地板转换器,它以类似的方式工作

https://github.com/blackrock/xml_to_parquet

于 2021-02-23T20:45:22.713 回答
0

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

于 2017-02-09T16:43:09.527 回答