Conversione da XML a JSON utilizzando Python?


170

Ho visto una buona parte del codice XML JSON sgraziato sul Web, e dopo aver interagito con gli utenti di Stack per un po ', sono convinto che questa folla possa aiutare più delle prime pagine dei risultati di Google.

Quindi, stiamo analizzando un feed meteorologico e dobbiamo popolare i widget meteo su una moltitudine di siti web. Stiamo esaminando ora le soluzioni basate su Python.

Questo feed RSS pubblico di weather.com è un buon esempio di ciò che analizzeremo (il nostro feed weather.com reale contiene informazioni aggiuntive a causa di una partnership con loro ).

In poche parole, come dovremmo convertire XML in JSON usando Python?

Risposte:


61

Non esiste una mappatura "one-to-one" tra XML e JSON, quindi la conversione l'una nell'altra richiede necessariamente una certa comprensione di ciò che si desidera fare con i risultati.

Detto questo, la libreria standard di Python ha diversi moduli per l'analisi di XML (inclusi DOM, SAX ed ElementTree). A partire da Python 2.6, il supporto per la conversione di strutture dati Python da e verso JSON è incluso nel jsonmodulo .

Quindi l'infrastruttura è lì.


2
xmljson IMHO è il più veloce da usare con il supporto per varie convenzioni pronte all'uso. pypi.org/project/xmljson
nitinr708 il

È già stato menzionato nelle risposte più recenti. Copre ancora solo un piccolo sottoinsieme di costrutti XML validi, ma probabilmente la maggior parte di ciò che le persone usano in pratica.
Dan Lenski,

281

xmltodict (divulgazione completa: l'ho scritto) può aiutarti a convertire il tuo XML in una struttura dict + list + string, seguendo questo "standard" . È basato su Expat , quindi è molto veloce e non è necessario caricare l'intero albero XML in memoria.

Una volta che hai quella struttura di dati, puoi serializzarla su JSON:

import xmltodict, json

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

@Martin Blech Se creo un file json dal mio file dei modelli django. Come posso mappare il mio file XML per convertire il file XML in JSON per i campi richiesti?
Sayth

1
@sayth Penso che dovresti pubblicare questo come una domanda SO separata.
Martin Blech,

@Martin Blech. Ho aggiunto una domanda, ma è piuttosto difficile per adattarsi a SO, io sono un principiante in modo aver fornito quante più informazioni possibile, ma mi aspetto che si può richiedere maggiore chiarezza stackoverflow.com/q/23676973/461887
sayth

Dopo così tanto tempo, sono un po 'sorpreso che xmltodict non sia una libreria "standard" in alcune distribuzioni di Linux. Anche se sembra fare il lavoro direttamente da quello che possiamo leggere, purtroppo userò un'altra soluzione come la conversione
xslt

Grazie mille per aver scritto questa fantastica biblioteca. Anche se bs4può fare il lavoro di xml per dettare, è estremamente facile usare la libreria
Tessaracter

24

È possibile utilizzare la libreria xmljson per la conversione utilizzando diverse convenzioni JSON XML .

Ad esempio, questo XML:

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

si traduce tramite la convenzione BadgerFish in questo:

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

e tramite la convenzione GData in questo (gli attributi non sono supportati):

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

... e tramite la convenzione Parker in questo (gli attributi non sono supportati):

{
  'p': 'text'
}

È possibile convertire da XML a JSON e da JSON a XML utilizzando le stesse convenzioni:

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

Divulgazione: ho scritto questa biblioteca. Spero che aiuti i futuri ricercatori.


4
È una libreria piuttosto interessante, ma per favore leggi Come offrire librerie open source personali? prima di pubblicare altre risposte mostrandole.
Martijn Pieters

1
Grazie @MartijnPieters - Ci sono appena passato e mi assicurerò di attenermi a questo.
S Anand,

1
Grazie Anand per la soluzione: sembra funzionare bene, non ha dipendenze esterne e offre molta flessibilità nel modo in cui gli attributi vengono gestiti usando le diverse convenzioni. Esattamente quello di cui avevo bisogno ed era la soluzione più flessibile e semplice che ho trovato.
mbbeme,

Grazie Anand - sfortunatamente, non riesco a far analizzare XML con la codifica utf8. Passando attraverso le fonti, sembra che il set di codifica tramite XMLParser (..) sia ignorato
Patrik Beck,

@PatrikBeck potresti condividere un piccolo esempio di XML con la codifica utf8 che non funziona?
S Anand,

11

Se qualche volta ottieni solo il codice di risposta invece di tutti i dati, l' errore come json parse sarà lì, quindi devi convertirlo come testo

import xmltodict

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

7

Ecco il codice che ho creato per questo. Non è possibile analizzare i contenuti, solo una semplice conversione.

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

7

Esiste un metodo per trasportare markup basato su XML come JSON che consente di riconvertirlo senza perdita di dati nella sua forma originale. Vedere http://jsonml.org/ .

È una specie di XSLT di JSON. Spero che lo trovi utile


7

A chiunque abbia ancora bisogno di questo. Ecco un nuovo codice semplice per fare questa conversione.

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

1
Funziona almeno in Python 3.7, anche se sfortunatamente aggiunge alcuni dati imprevisti ai nomi delle chiavi se alcuni valori sono nel tuo xml, ad esempio un tag xmlns su un nodo a livello di radice appare in ogni chiave di nodo in questo modo: {'{ maven .apache.org / POM / 4.0.0 } artifactId ':' test-service ', che proviene da xml in questo modo: <project xmlns = " maven.apache.org/POM/4.0.0 " xsi: schemaLocation = " maven .apache.org / POM / 4.0.0 maven.apache.org/xsd/maven-4.0.0.xsd "xmlns: xsi =" w3.org/2001/XMLSchema-instance "> <modelVersion> 4.0.0 </ modelVersion>
hrbdg

5

Si consiglia di dare un'occhiata a http://designtheory.org/library/extrep/designdb-1.0.pdf . Questo progetto inizia con una conversione da XML a JSON di una grande libreria di file XML. Sono state fatte molte ricerche nella conversione, ed è stata prodotta la più semplice e intuitiva mappatura XML -> JSON (è descritta all'inizio del documento). In breve, converti tutto in un oggetto JSON e metti i blocchi ripetuti come un elenco di oggetti.

oggetti che significano coppie chiave / valore (dizionario in Python, hashmap in Java, oggetto in JavaScript)

Non è possibile eseguire il mapping su XML per ottenere un documento identico, il motivo è che non è noto se una coppia chiave / valore fosse un attributo o un <key>value</key>, quindi le informazioni vanno perse.

Se me lo chiedi, gli attributi sono un trucco per iniziare; poi di nuovo hanno funzionato bene per HTML.


4

Bene, probabilmente il modo più semplice è semplicemente analizzare l'XML in dizionari e quindi serializzarlo con simplejson.


4

Suggerirei di non optare per una conversione diretta. Converti XML in un oggetto, quindi dall'oggetto in JSON.

A mio avviso, ciò fornisce una definizione più chiara di come XML e JSON corrispondono.

Ci vuole tempo per avere ragione e potresti persino scrivere strumenti per aiutarti a generarne una parte, ma sarebbe simile a questo:

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

2

Ho trovato per semplici snapshot XML, l'uso dell'espressione regolare risparmierebbe problemi. Per esempio:

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

Per farlo analizzando XML, come diceva @Dan, non esiste una soluzione unica perché i dati sono diversi. Il mio consiglio è di usare lxml. Anche se non finito con json, lxml.objectify dà buoni risultati tranquilli:

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

1
ma rimuove i nodi duplicati
Pooya

2

Mentre le librerie integrate per l'analisi XML sono abbastanza buone, sono parziale a lxml .

Ma per l'analisi dei feed RSS, consiglierei Universal Feed Parser , che può anche analizzare Atom. Il suo principale vantaggio è che può digerire anche la maggior parte dei mangimi malformati.

Python 2.6 include già un parser JSON, ma una versione più recente con velocità migliorata è disponibile come simplejson .

Con questi strumenti la creazione della tua app non dovrebbe essere così difficile.


2

La mia risposta affronta il caso specifico (e piuttosto comune) in cui non è davvero necessario convertire l'intero XML in JSON, ma ciò di cui hai bisogno è attraversare / accedere a parti specifiche dell'XML, e hai bisogno che sia veloce , e semplice (usando operazioni tipo json / dict).

Approccio

Per questo, è importante notare che l'analisi di un XML per etree usando lxmlè super veloce. La parte lenta nella maggior parte delle altre risposte è il secondo passaggio: attraversare la struttura etree (di solito in terra di pitone), convertendola in json.

Il che mi porta all'approccio che ho trovato migliore per questo caso: analizzare l'xml usando lxmle quindi avvolgere i nodi etree (pigramente), fornendo loro un'interfaccia simil-dict.

Codice

Ecco il codice:

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

Questa implementazione non è completa, ad esempio, non supporta in modo chiaro casi in cui un elemento ha sia testo che attributi o testo e figli (solo perché non ne avevo bisogno quando l'ho scritto ...) Dovrebbe essere facile per migliorarlo, però.

Velocità

Nel mio caso specifico impiego, dove avevo bisogno di solo elementi di processo specifici del xml, questo approccio ha dato una sorprendente e aumento di velocità sorprendente di un fattore di 70 (!) Rispetto all'utilizzo di @ Martin Blech xmltodict e poi attraversando direttamente il dict.

indennità

Come bonus, dal momento che la nostra struttura è già simile a una regola, otteniamo un'altra implementazione alternativa di xml2jsongratuitamente. Dobbiamo solo passare la nostra struttura simile a un dict json.dumps. Qualcosa di simile a:

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

Se il tuo xml include attributi, dovrai usare un po 'alfanumerico attr_prefix (ad esempio "ATTR_"), per assicurarti che le chiavi siano chiavi json valide.

Non ho confrontato questa parte.


Se provo a farlo json.dumps(tree), l' oggetto del tipo "ETreeDictWrapper" non è serializzabile JSON
Vlad T.

2

Quando faccio qualsiasi cosa con XML in Python, uso quasi sempre il pacchetto lxml. Sospetto che la maggior parte delle persone usi lxml. Puoi usare xmltodict ma dovrai pagare la penalità di analizzare nuovamente l'XML.

Per convertire XML in json con lxml tu:

  1. Analizza documento XML con lxml
  2. Converti lxml in un dict
  3. Converti l'elenco in json

Uso la seguente classe nei miei progetti. Utilizzare il metodo 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)

L'output dal main incorporato è:

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

Quale è una trasformazione di questo XML:

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




1

Puoi usare declxml. Ha funzionalità avanzate come multi attributi e supporto nidificato complesso. Devi solo scrivere un semplice processore per questo. Inoltre, con lo stesso codice, è possibile riconvertire anche in JSON. È abbastanza semplice e la documentazione è fantastica.

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


-1

Preparare i dati in Python : per creare JSON prima è necessario preparare i dati in Python. Possiamo usare Elenco e Dizionario in Python per preparare i dati.

Elenco Python <==> Array JSON

Dizionario Python <==> Oggetto JSON (formato valore chiave) Controllare questo per maggiori dettagli

https://devstudioonline.com/article/create-json-and-xml-in-python


Benvenuto in Stack Overflow! Sebbene i collegamenti siano un ottimo modo per condividere le conoscenze, in realtà non risponderanno alla domanda se si romperanno in futuro. Aggiungi alla tua risposta il contenuto essenziale del link che risponde alla domanda. Nel caso in cui il contenuto sia troppo complesso o troppo grande per adattarsi qui, descrivere l'idea generale della soluzione proposta. Ricordarsi di mantenere sempre un riferimento al collegamento al sito Web della soluzione originale. Vedi: Come posso scrivere una buona risposta?
sɐunıɔ ןɐ qɐp

-4

Per rappresentare i dati in formato JSON

name=John
age=20
gender=male
address=Sector 12 Greater Kailash, New Delhi
Jobs=Noida,Developer | Gurugram,Tester |Faridabad,Designer

In json rispondiamo a dati in formato chiave e valore

{
    "name":"john",
    "age":20,
    "gender":"male",
    "address":["New kP college","Greater Kailash","New Delhi"],
    "jobs":[
               {"Place":"Noida","Title":"Developer "},
               {"Place":"Gurugram","Title":"Tester "},
               {"Place":"Faridabad","Title":"Designer"}
           ]
}

Per rappresentare i dati in formato XML

<!-- In xml we write a code under a key you can take any key -->
<info> <!-- key open -->

<name> john </name> 
<age> 20 </age>
<gender> male </gender>

<address> 
<item> New kP college </item>
<item> Greater Kailash </item>
<item> New Delhi </item>
</address>

<jobs>
 <item>
  <title>Developer </title>
  <place>Noida</place>
 </item>

 <item>
  <title>Designer</title>
  <place>Gurugram</place>
 </item>
 
 <item>
  <title>Developer </title>
  <place>Faridabad</place>
 </item>
</jobs>

</info> <!-- key close-->

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.