Conversion de XML en JSON à l'aide de Python?

170

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

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

Ce flux RSS public de weather.com est un bon exemple de ce que nous analyserions ( notre flux actuel de weather.com 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?

Pete Karl II
la source

Réponses:

61

Il n'y a pas de mappage «un-à-un» entre XML et JSON, donc la conversion de l'un à l'autre nécessite nécessairement 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 d'analyse XML (notamment DOM, SAX et ElementTree). Depuis Python 2.6, la prise en charge de la conversion des structures de données Python vers et depuis JSON est incluse dans le jsonmodule .

Donc, l'infrastructure est là.

Dan Lenski
la source
2
xmljson IMHO est le plus rapide à utiliser avec le support de diverses conventions hors de la boîte. pypi.org/project/xmljson
nitinr708
Cela a déjà été mentionné dans les nouvelles réponses. Il ne couvre toujours qu'un petit sous-ensemble de constructions XML valides, mais probablement la majorité de ce que les gens utilisent dans la pratique.
Dan Lenski
281

xmltodict (divulgation complète: je l'ai écrit) peut vous aider à convertir votre XML en une structure dict + liste + chaîne, suivant ce "standard" . Il est basé sur Expat , donc il est très rapide et n'a pas besoin de charger l'arborescence XML entière 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"]}}'
Martin Blech
la source
@Martin Blech Si je crée un fichier json à partir de mon fichier de modèles django. Comment puis-je mapper mon fichier xml pour convertir le xml en json pour les champs obligatoires?
sayth
1
@sayth Je pense que vous devriez poster ceci comme une question SO distincte.
Martin Blech
@Martin Blech. J'ai ajouté une question, mais c'est assez difficile à intégrer dans SO, je suis un débutant, j'ai donc fourni autant d'informations que possible, mais je suppose que vous aurez peut-être besoin de plus de clarté stackoverflow.com/q/23676973/461887
dis
Après si longtemps, je suis un peu surpris que xmltodict ne soit pas une bibliothèque "standard" dans certaines distributions Linux. Bien que cela semble faire le travail directement à partir de ce que nous pouvons lire, j'utiliserai malheureusement une autre solution comme la conversion xslt
sancelot
Merci beaucoup d'avoir écrit cette fantastique bibliothèque. Bien que vous bs4puissiez faire le travail de xml pour dicter, il est extrêmement facile d'utiliser la bibliothèque
Tessaracter
24

Vous pouvez utiliser la bibliothèque xmljson pour convertir à l'aide de différentes conventions XML JSON .

Par exemple, ce XML:

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

se traduit par la convention BadgerFish en ceci:

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

et via la convention GData dans ceci (les attributs ne sont pas pris en charge):

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

... et via la convention Parker dans ceci (les attributs ne sont pas pris en charge):

{
  'p': 'text'
}

Il est possible de convertir de 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.

S Anand
la source
4
C'est une bibliothèque plutôt cool, mais s'il vous plaît, lisez Comment offrir des bibliothèques open-source personnelles? avant de publier plus de réponses pour le montrer.
Martijn Pieters
1
Merci @MartijnPieters - Je viens de passer par là et je vais m'assurer de m'en tenir à cela.
S Anand
1
Merci Anand pour la solution - cela semble bien fonctionner, n'a pas de dépendances externes et offre beaucoup de flexibilité dans la façon dont les attributs sont gérés en utilisant les différentes conventions. Exactement ce dont j'avais besoin et c'était la solution la plus flexible et la plus simple que j'ai trouvée.
mbbeme
Merci Anand - malheureusement, je ne peux pas le faire analyser XML avec le codage utf8. En passant par les sources, il semble que le jeu d'encodage via XMLParser (..) soit ignoré
Patrik Beck
@PatrikBeck pourriez-vous s'il vous plaît partager un petit exemple de XML avec un encodage utf8 qui casse?
S Anand
11

Si un certain temps vous n'obtenez que le code de réponse au lieu de toutes les données, une erreur comme json parse sera donc là, vous devez donc le convertir en texte

import xmltodict

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

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 __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()
Paulo Vj
la source
7

Il existe une méthode pour transporter le balisage XML au format JSON qui lui permet d'être reconverti 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

themihai
la source
7

À tous ceux qui peuvent encore en avoir besoin. Voici un nouveau code 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
jnhustin
la source
1
Il fonctionne au moins en Python 3.7, bien que malheureusement, il ajoute des données inattendues aux noms de clé si certaines valeurs sont dans votre xml, par exemple une balise xmlns sur un nœud de niveau racine apparaît dans chaque clé de nœud comme ceci: {'{ maven .apache.org / POM / 4.0.0 } artifactId ':' test-service ', qui provient de xml comme ceci: <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

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

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

Il n'y a pas de mappage vers 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>, donc cette information est perdue.

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

pykler
la source
4

Eh bien, le moyen le plus simple est probablement d'analyser le XML dans des dictionnaires et de le sérialiser avec simplejson.

dguaraglia
la source
4

Je suggérerais de ne pas opter pour une conversion directe. Convertissez XML en objet, puis de l'objet en JSON.

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

Il faut du temps pour bien faire 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 ):
    ...
Michael Anderson
la source
2

J'ai trouvé pour de simples extraits XML, utiliser une expression régulière éviterait 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 par analyse XML, comme l'a dit @Dan, il n'y a pas de solution unique car les données sont différentes. Ma suggestion est d'utiliser lxml. Bien que n'étant pas fini avec 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'
Andrew_1510
la source
1
mais il supprime les nœuds en double
Pooya
2

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

Mais pour analyser les flux RSS, je recommanderais Universal Feed Parser , qui peut également analyser Atom. Son principal avantage est qu'il peut digérer même les aliments les plus mal formés.

Python 2.6 inclut déjà un analyseur JSON, mais une version plus récente avec une vitesse améliorée est disponible en tant que simplejson .

Avec ces outils, la création de votre application ne devrait pas être si difficile.

Luka Marinko
la source
2

Ma réponse aborde le cas spécifique (et quelque peu courant) où 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 pour être rapide , et simple (en utilisant des opérations de type json / dict).

Approche

Pour cela, il est important de noter que l'analyse d'un xml vers etree en utilisant lxmlest super rapide. La partie lente dans la plupart des autres réponses est la deuxième passe: traverser la structure etree (généralement en python-land), la convertir en json.

Ce qui m'amène à l'approche que j'ai trouvée la meilleure pour ce cas: analyser le XML en utilisant lxml, puis envelopper 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 prend pas en charge proprement les cas où un élément a à la fois du texte et des attributs, ou à la fois du texte et des enfants (uniquement parce que je n'en avais pas besoin lorsque je l'ai écrit ...) Cela devrait être facile pour l'améliorer, cependant.

La vitesse

Dans mon cas d'utilisation spécifique, où je devais seulement des éléments spécifiques du processus du xml, cette approche a donné une surprenante et speedup frappe par un facteur de 70 (!) Par rapport à l' utilisation de @ Martin Blech de xmltodict puis traversant le dict directement.

Prime

En prime, comme notre structure est déjà de type dict, nous obtenons une autre implémentation alternative de xml2jsongratuitement. 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 inclut des attributs, vous devez utiliser des caractères alphanumériques attr_prefix(par exemple "ATTR_"), pour vous assurer que les clés sont des clés json valides.

Je n'ai pas évalué cette partie.

shx2
la source
Si j'essaye de le faire, json.dumps(tree)il dit que l' objet de type 'ETreeDictWrapper' n'est pas sérialisable JSON
Vlad T.
2

Quand je fais quoi que ce soit avec XML en python, j'utilise presque toujours le package lxml. Je soupçonne que la plupart des gens utilisent lxml. Vous pouvez utiliser xmltodict mais vous devrez payer la pénalité pour analyser à nouveau le XML.

Pour convertir XML en json avec lxml, vous:

  1. Analyser un document XML avec lxml
  2. Convertir lxml en dict
  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 __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)

La sortie du main intégré est:

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

Qui est une transformation de ce xml:

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

Ce truc ici est activement maintenu et jusqu'à présent est mon préféré: xml2json en python

truthadjustr
la source
1

consultez lxml2json (divulgation: je l'ai écrit)

https://github.com/rparelius/lxml2json

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

Robert Parelius
la source
1

Vous pouvez utiliser declxml. Il possède des fonctionnalités avancées telles que des attributs multiples et une prise en charge imbriquée complexe. Il vous suffit 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

srth12
la source
-1

Préparer les données en 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.

Liste Python <==> Tableau JSON

Dictionnaire Python <==> Objet JSON (format de valeur clé) Cochez cette case pour plus de détails

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

Anushree Anisha
la source
Bienvenue dans Stack Overflow! Bien que les liens soient un excellent moyen de partager des connaissances, ils ne répondront pas vraiment à la question s'ils sont rompus à l'avenir. Ajoutez à votre réponse le contenu essentiel du lien qui répond à la question. Au cas où le contenu serait trop complexe ou trop volumineux pour tenir ici, décrivez l'idée générale de la solution proposée. N'oubliez pas de toujours conserver un lien de référence vers le site Web de la solution d'origine. Voir: Comment écrire une bonne réponse?
sɐunıɔ ןɐ qɐp
-4

Pour représenter des données au format JSON

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

Dans json, nous représentons une donnée au format clé et valeur

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

Pour représenter des données au format 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-->

Anushree Anisha
la source