XML converteren naar JSON met Python?

Ik heb behoorlijk wat lompe XML->JSON-code op internet gezien, en na een tijdje contact te hebben gehad met de gebruikers van Stack, ben ik ervan overtuigd dat deze menigte meer kan helpen dan de eerste paar pagina’s met Google-resultaten kan.

We zijn dus een weerfeed aan het ontleden en we moeten weerwidgets vullen op een groot aantal websites. We onderzoeken nu op Python gebaseerde oplossingen.

Deze openbare weather.com RSS feedis een goed voorbeeld van wat we zouden analyseren (onze actuele weather.com-feed bevat aanvullende informatie vanwege een samenwerking met hen).

In een notendop, hoe moeten we XML naar JSON converteren met Python?


Antwoord 1, autoriteit 100%

Er is geen “één-op-één”-toewijzing tussen XML en JSON, dus het converteren van de ene naar de andere vereist enig begrip van wat u met de resultaten doenwilt doen.

Dat gezegd hebbende, de standaardbibliotheek van Python heeft verschillende modules voor het ontleden van XML( inclusief DOM, SAX en ElementTree). Vanaf Python 2.6 is ondersteuning voor het converteren van Python-gegevensstructuren van en naar JSON opgenomen in de json-module.

Dus de infrastructuur is er.


Antwoord 2, autoriteit 93%

xmltodict (volledige openbaarmaking: ik heb het geschreven) kan u helpen om uw XML te converteren naar een DICT + -lijst + Stringstructuur, na deze ” standaard “. Het is expat -based, dus het is erg snel en hoeft het geheel niet te laden XML-boom in het geheugen.

Zodra u die gegevensstructuur hebt, kunt u het serialiseren aan JSON:

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

Antwoord 3, Autoriteit 46%

U kunt de xmljson bibliotheek gebruiken om te converteren met verschillende XML JSON Conventions .

Deze XML bijvoorbeeld:

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

Vertaalt via de Badgerfish Convention hierin:

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

en via de GDATA-conventie hierin (attributen worden niet ondersteund):

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

… en via de Parker Convention hierin (attributen worden niet ondersteund):

{
  'p': 'text'
}

Het is mogelijk om van XML naar JSON en van JSON naar XML te converteren met hetzelfde
Conventies:

>>> 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>]

Openbaarmaking: ik heb deze bibliotheek geschreven. Hoop dat het toekomstige zoekers helpt.


Antwoord 4, Autoriteit 22%

Als u een tijdje alleen Reaction Code krijgt in plaats van alle gegevens, dan is Fout als JSON Parse er zijn er, zodat u het wilt converteren als tekst

import xmltodict
data = requests.get(url)
xpars = xmltodict.parse(data.text)
json = json.dumps(xpars)
print json 

Antwoord 5, Autoriteit 18%

Aan iedereen die dit nog steeds nodig heeft. Hier is een nieuwere, eenvoudige code om deze conversie te doen.

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

Antwoord 6, Autoriteit 14%

Hier is de code die ik daarvoor heb gemaakt. De inhoud wordt niet geparseerd, alleen conversie.

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()

Antwoord 7, autoriteit 11%

Er is een methode om op XML gebaseerde opmaak als JSON te transporteren, waardoor deze zonder verlies terug kan worden geconverteerd naar de oorspronkelijke vorm. Zie http://jsonml.org/.

Het is een soort XSLT van JSON. Ik hoop dat je het nuttig vindt


Antwoord 8, autoriteit 9%

Ik raad aan om niet voor een directe conversie te gaan. Converteer XML naar een object en vervolgens van het object naar JSON.

Naar mijn mening geeft dit een duidelijkere definitie van hoe de XML en JSON overeenkomen.

Het kost tijd om het goed te krijgen en je kunt zelfs tools schrijven om je te helpen een deel ervan te genereren, maar het ziet er ongeveer zo uit:

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 ):
    ...

Antwoord 9, autoriteit 8%

Misschien wil je eens kijken op http://designtheory.org/library/ extrep/designdb-1.0.pdf. Dit project begint met een XML-naar-JSON-conversie van een grote bibliotheek met XML-bestanden. Er is veel onderzoek gedaan naar de conversie en de meest eenvoudige intuïtieve XML -> JSON-toewijzing is geproduceerd (deze wordt vroeg in het document beschreven). Samengevat, converteer alles naar een JSON-object en plaats herhalende blokken als een lijst met objecten.

objecten die sleutel/waarde-paren betekenen (woordenboek in Python, hashmap in Java, object in JavaScript)

Er is geen toewijzing terug naar XML om een identiek document te krijgen, de reden is dat het onbekend is of een sleutel/waarde-paar een attribuut of een <key>value</key>was , daarom gaat die informatie verloren.

Als je het mij vraagt, zijn attributen een hack om te beginnen; aan de andere kant werkten ze goed voor HTML.


Antwoord 10, autoriteit 6%

Nou, waarschijnlijk is de eenvoudigste manier om de XML te ontleden in woordenboeken en dat vervolgens te serialiseren met simplejson.


Antwoord 11, autoriteit 6%

Als ik iets doe met XML in python, gebruik ik bijna altijd het lxml-pakket. Ik vermoed dat de meeste mensen lxml gebruiken. Je zou xmltodict kunnen gebruiken, maar je zult de boete moeten betalen voor het opnieuw ontleden van de XML.

Om XML naar json te converteren met lxml:

  1. XML-document ontleden met lxml
  2. Lxml converteren naar een dictaat
  3. Lijst converteren naar json

Ik gebruik de volgende klasse in mijn projecten. Gebruik de toJson-methode.

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)

De uitvoer van de ingebouwde hoofd is:

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

Wat is een transformatie van deze XML:

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

Antwoord 12, Autoriteit 3%

Ik vond voor eenvoudige XML-knipsels, gebruik reguliere expressie zou problemen redden. Bijvoorbeeld:

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

Om het te doen door XML-parsing, zoals @dan zei, is er geen one-for-all-oplossing omdat de gegevens anders zijn. Mijn suggestie is om LXML te gebruiken. Hoewel niet klaar met JSON, lxml.objectify geven rustige goede resultaten:

>>> 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'

Antwoord 13, autoriteit 3%

Hoewel de ingebouwde bibliotheken voor XML-parsing redelijk goed zijn, ben ik een voorstander van lxml.

Maar voor het ontleden van RSS-feeds raad ik Universal Feed Parseraan, die ook ontleden atoom.
Het belangrijkste voordeel is dat het zelfs de meeste misvormde voeders kan verteren.

Python 2.6 bevat al een JSON-parser, maar een nieuwere versie met verbeterde snelheidis beschikbaar als simplejson.

Met deze tools zou het bouwen van je app niet zo moeilijk moeten zijn.


Antwoord 14, autoriteit 3%

Mijn antwoord spreekt de specifieke (en enigszins gemeenschappelijke) zaak aan waar u niet echt de volledige XML naar JSON hoeven te converteren, maar wat u nodig heeft, is om specifieke delen van de XML te traven / toegang te krijgen , en u hebt het nodig om snel en eenvoudig (met behulp van JSON / DICT-achtige bewerkingen).

Benadering

Hiervoor is het belangrijk om op te merken dat het parseren van een XML naar Etree met lxmlSuper snel is. Het langzame deel in de meeste andere antwoorden is de tweede pas: het doorkruisen van de Etree-structuur (meestal in Python-Land), en het omzetten naar JSON.

die mij naar de aanpak leidt die ik het beste vond voor deze zaak: het parseren van de XML met lxml, en vervolgens de Etree-knooppunten (lui) inpakken, waardoor ze een DICT-achtige interface bieden.

Code

Hier is de 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),
    )

Deze implementatie is niet compleet, bijvoorbeeld, het ondersteunt geen cases waarbij een element zowel tekst als attributen heeft, of zowel tekst als kinderen (alleen omdat ik het niet nodig had toen ik het had geschreven Moet het eenvoudig zijn om het te verbeteren.

snelheid

In mijn specifieke gebruikscase, waar ik alleen specifieke elementen van de XML moest verwerken, gaf deze aanpak een verrassing en opvallend versnellen met een factor van 70 (!) in vergelijking met het gebruik van @Martin Blech’s XMLTODICT en vervolgens de DICT rechtstreeks doorkruisen.

BONUS

Als een bonus, omdat onze structuur al dict-achtig is, krijgen we nog een andere alternatieve implementatie van xml2jsongratis. We moeten alleen onze dictachtige structuur doorgeven aan json.dumps. Zoiets als:

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

Als uw xml kenmerken bevat, moet u een alfanumeriek attr_prefix(bijv. “ATTR_”) gebruiken om ervoor te zorgen dat de sleutels geldige json-sleutels zijn.

Ik heb dit onderdeel niet gebenchmarkt.


Antwoord 15, autoriteit 3%

bekijk lxml2json (openbaarmaking: ik heb het geschreven)

https://github.com/rparelius/lxml2json

het is erg snel, lichtgewicht (alleen lxml vereist), en een voordeel is dat je controle hebt over of bepaalde elementen worden geconverteerd naar lijsten of dicts


Antwoord 16, autoriteit 2%

jsonpickleof als je feedparser gebruikt, kun je feed_parser_to_json.py


Antwoord 17, autoriteit 2%

U kunt declxml gebruiken. Het heeft geavanceerde functies zoals multi-attributen en complexe geneste ondersteuning. Je hoeft er alleen maar een simpele processor voor te schrijven. Met dezelfde code kunt u ook terug naar JSON converteren. Het is vrij eenvoudig en de documentatie is geweldig.

Link: https://declxml.readthedocs.io/en/latest/index .html


Antwoord 18, autoriteit 2%

Ik heb er een tijdje geleden een op github gepubliceerd..

https://github.com/davlee1972/xml_to_json

Deze converter is geschreven in Python en zal een of meer XML-bestanden converteren naar JSON / JSONL-bestanden

Het vereist een XSD-schemabestand om geneste json-structuren (woordenboeken versus lijsten) en json-equivalente gegevenstypen te achterhalen.

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

Ik heb ook een vervolg xml naar parket converter die op een vergelijkbare manier werkt

https://github.com/blackrock/xml_to_parquet


Antwoord 19

Dit spul hier wordt actief onderhouden en is tot nu toe mijn favoriet: xml2json in python


Antwoord 20

Als je geen externe bibliotheken en tools van derden wilt gebruiken, probeer dan onderstaande code.

Code

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',''))))

monster invoer

<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>

output

[
  {
    "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"
                      }
                    ]
                  }
                ]
              }
            ]
          }
        ]
      }
    ]
  }
]

Other episodes