web-dev-qa-db-fra.com

Conversion de XML en JSON avec Python?

J'ai vu pas mal de code XML-> JSON disgracieux sur le Web et, après avoir un peu échangé avec les utilisateurs de Stack, je suis convaincu que cette foule peut aider plus que les premières pages de résultats de Google.

Nous analysons donc un flux météo et nous devons renseigner des widgets météo sur une multitude de sites Web. Nous examinons maintenant des solutions basées sur Python.

Ce flux RSS public weather.com est un bon exemple de ce que nous analyserions (notre flux weather.com actuel contient des informations supplémentaires en raison d’un partenariat avec eux).

En un mot, comment devrions-nous convertir XML en JSON en utilisant Python?

137
Pete Karl II

Il n'y a pas de mappage "un à un" entre XML et JSON, la conversion de l'un à l'autre nécessite donc une certaine compréhension de ce que vous voulez faire avec les résultats.

Cela étant dit, la bibliothèque standard de Python contient plusieurs modules pour analyser XML (y compris DOM, SAX et ElementTree). Depuis Python 2.6, la conversion de structures de données Python en JSON est incluse dans le module json .

Donc, l'infrastructure est là.

51
Dan Lenski

xmltodict (divulgation complète: je l'ai écrit) peut vous aider à convertir votre code XML en une structure dict + list + string, en suivant ce "standard" . Il est basé sur Expat -, il est donc très rapide et n’a pas besoin de charger tout l’arbre XML en mémoire.

Une fois que vous avez cette structure de données, vous pouvez la sérialiser en JSON:

import xmltodict, json

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

Vous pouvez utiliser la bibliothèque xmljson pour convertir en utilisant différentes conventions XML ​​JSON .

Par exemple, ce XML:

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

traduit via la convention BadgerFish en ceci:

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

et via la convention GData dans ceci (les attributs ne sont pas supportés):

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

... et via la convention Parker dans ceci (les attributs ne sont pas supportés):

{
  'p': 'text'
}

Il est possible de convertir XML en JSON et de JSON en XML en utilisant les mêmes conventions :

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

Divulgation: J'ai écrit cette bibliothèque. J'espère que cela aidera les futurs chercheurs.

16
S Anand

Voici le code que j'ai construit pour cela. Il n'y a pas d'analyse du contenu, juste une simple conversion.

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 __== '__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
Paulo Vj

Il existe une méthode de transport du balisage XML en tant que JSON qui lui permet d'être reconvertie sans perte dans sa forme d'origine. Voir http://jsonml.org/

C'est une sorte de XSLT de JSON. J'espère que cela vous aidera 

6
themihai

Si, à un moment donné, vous n'obtenez que code de réponse au lieu de toutes les données, alors error comme json parse sera présent, vous devrez donc le convertir en text

import xmltodict

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

Vous voudrez peut-être consulter http://designtheory.org/library/extrep/designdb-1.0.pdf . Ce projet commence par la conversion XML en JSON d'une grande bibliothèque de fichiers XML. De nombreuses recherches ont été effectuées lors de la conversion et le mappage intuitif XML -> JSON le plus simple a été produit (il est décrit au début du document). En résumé, convertissez tout en objet JSON et placez les blocs répétitifs en tant que liste d'objets.

objets signifiant des paires clé/valeur (dictionnaire en Python, hashmap en Java, objet en JavaScript)

Il n'y a pas de correspondance en XML pour obtenir un document identique. La raison en est qu'on ne sait pas si une paire clé/valeur était un attribut ou un <key>value</key>; par conséquent, cette information est perdue. 

Si vous me demandez, les attributs sont un hack pour commencer; là encore ils ont bien fonctionné pour HTML.

5
pykler

Eh bien, le moyen le plus simple est probablement d’analyser le XML en dictionnaires, puis de le sérialiser avec simplejson. 

4
dguaraglia

Je suggère de ne pas aller pour une conversion directe. Convertissez XML en objet, puis de l'objet en JSON.

À mon avis, cela donne une définition plus précise de la correspondance entre XML et JSON.

Il faut du temps pour bien faire les choses et vous pouvez même écrire des outils pour vous aider à en générer une partie, mais cela ressemblerait à peu près à ceci:

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 ):
    ...
3
Michael Anderson

Bien que les bibliothèques intégrées pour l'analyse XML soient assez bonnes, je suis partisan de lxml .

Mais pour analyser les flux RSS, je recommanderais Universal Feed Parser , qui peut également analyser Atom . Son principal avantage est de pouvoir digérer même les flux les plus malformés.

Python 2.6 inclut déjà un analyseur JSON, mais une version plus récente de avec une vitesse améliorée est disponible sous la forme simplejson .

Avec ces outils, créer votre application ne devrait pas être aussi difficile.

2
Luka Marinko

Quand je fais quelque chose avec XML en python, j'utilise presque toujours le paquet lxml. Je soupçonne que la plupart des gens utilisent lxml. Vous pouvez utiliser xmltodict mais vous devrez payer la peine d’analyser à nouveau le XML.

Pour convertir XML en json avec lxml, vous devez:

  1. Analyser un document XML avec lxml
  2. Convertir lxml en dictée
  3. Convertir la liste en json

J'utilise la classe suivante dans mes projets. Utilisez la méthode 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 __== "__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)

Le résultat de la fonction intégrée main est:

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

Ce qui est une transformation de ce xml:

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

Ce matériel est maintenu activement et jusqu’à présent, c’est mon préféré: xml2json en python

1
typelogic

À tous ceux qui en ont encore besoin. Voici un code plus récent et simple pour effectuer cette conversion.

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
jnhustin

Vous pouvez utiliser declxml. Il possède des fonctionnalités avancées telles que les attributs multiples et une prise en charge complexe imbriquée. Vous avez juste besoin d'écrire un processeur simple pour cela. Également avec le même code, vous pouvez également reconvertir en JSON. C'est assez simple et la documentation est géniale.

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

1
srth12

J'ai trouvé que, pour de simples fragments XML, l'utilisation d'une expression régulière permettrait d'éviter des problèmes. Par exemple:

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

Pour le faire en analysant XML, comme @Dan l'a dit, il n'y a pas de solution unique, car les données sont différentes. Ma suggestion est d'utiliser LXML. Bien que pas fini de json, lxml.objectify donne de bons résultats:

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

vérifier lxml2json (divulgation: je l'ai écrit) 

https://github.com/rparelius/lxml2json

c'est très rapide, léger (ne nécessite que lxml), et un avantage est que vous avez le contrôle sur la conversion de certains éléments en listes ou en dict

1
Robert Parelius

jsonpickle ou si vous utilisez feedparser, vous pouvez essayer feed_parser_to_json.py

1
choonkeat

Ma réponse concerne le cas spécifique (et plutôt courant) dans lequel vous n'avez pas vraiment besoin de convertir le xml entier en json, mais ce dont vous avez besoin est de parcourir/accéder à des parties spécifiques du xml, et vous en avez besoin be fast, et simple (en utilisant des opérations similaires à json/dict).

Approche

Pour cela, il est important de noter que l'analyse d'un fichier xml dans etree à l'aide de lxml est extrêmement rapide. La deuxième étape est la partie lente de la plupart des autres réponses: parcourir la structure etree (généralement en python-land) et la convertir en json.

Ce qui me conduit à l’approche que j’ai trouvée la meilleure pour ce cas: analyser le fichier XML à l’aide de lxml, puis encapsuler les nœuds etree (paresseusement), en leur fournissant une interface de type dict.

Code

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

Cette implémentation n'est pas complète, par exemple, elle ne supporte pas proprement les cas où un élément a à la fois du texte et des attributs, ou du texte et des enfants (uniquement parce que je n'en avais pas besoin quand je l'ai écrit ...) Cela devrait être facile pour l'améliorer, cependant.

La vitesse

Dans mon cas d'utilisation spécifique, où je ne devais traiter que des éléments spécifiques du xml, cette approche donnait une accélération surprenante et saisissante d'un facteur 70 (!) Par rapport à l'utilisation de xmltodict de @Martin Blech puis traverser le dict directement.

Prime

En prime, comme notre structure est déjà semblable à celle d'un dicton, nous obtenons gratuitement une autre implémentation de xml2json. Nous avons juste besoin de passer notre structure de type dict à json.dumps. Quelque chose comme:

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

Si votre xml comprend des attributs, vous devez utiliser un attr_prefix alphanumérique (par exemple, "ATTR_") pour vous assurer que les clés sont des clés json valides.

Je n'ai pas évalué cette partie.

1
shx2

Préparer les données dans Python : Pour créer JSON , vous devez d'abord préparer les données en python. Nous pouvons utiliser List et Dictionary en Python pour préparer les données.

Python List <==> JSON Array

Python Dictionnaire <==> JSON Object (Key Value Format) Cochez cette option pour plus de détails

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

0
Anushree Anisha