¿Convertir XML a JSON usando Python?

170

He visto una buena cantidad de código XML-> JSON desgarbado en la web, y después de haber interactuado un poco con los usuarios de Stack, estoy convencido de que esta multitud puede ayudar más que las primeras páginas de resultados de Google.

Por lo tanto, estamos analizando una fuente de información meteorológica, y necesitamos poblar widgets meteorológicos en una multitud de sitios web. Estamos buscando soluciones basadas en Python.

Este feed RSS público de weather.com es un buen ejemplo de lo que estaríamos analizando ( nuestro feed de weather.com real contiene información adicional debido a una asociación con ellos ).

En pocas palabras, ¿cómo deberíamos convertir XML a JSON usando Python?

Pete Karl II
fuente

Respuestas:

61

No existe un mapeo "uno a uno" entre XML y JSON, por lo que la conversión de uno a otro necesariamente requiere cierta comprensión de lo que desea hacer con los resultados.

Dicho esto, la biblioteca estándar de Python tiene varios módulos para analizar XML (incluidos DOM, SAX y ElementTree). A partir de Python 2.6, el jsonmódulo incluye soporte para convertir estructuras de datos de Python ay desde JSON .

Entonces la infraestructura está ahí.

Dan Lenski
fuente
2
xmljson en mi humilde opinión es el más rápido de usar con soporte para varias convenciones fuera de la caja. pypi.org/project/xmljson
nitinr708
Ya se ha mencionado en nuevas respuestas. Todavía solo cubre un pequeño subconjunto de construcciones XML válidas, pero probablemente la mayoría de lo que la gente usa en la práctica.
Dan Lenski
281

xmltodict (divulgación completa: lo escribí) puede ayudarlo a convertir su XML a una estructura dict + list + string, siguiendo este "estándar" . Está basado en Expat , por lo que es muy rápido y no necesita cargar todo el árbol XML en la memoria.

Una vez que tenga esa estructura de datos, puede serializarla a JSON:

import xmltodict, json

o = xmltodict.parse('<e> <a>text</a> <a>text</a> </e>')
json.dumps(o) # '{"e": {"a": ["text", "text"]}}'
Martin Blech
fuente
@ Martin Blech Si creo un archivo json desde mi archivo de modelos django. ¿Cómo puedo asignar mi archivo xml para convertir el xml a json para los campos requeridos?
dice
1
@sayth Creo que deberías publicar esto como una pregunta SO separada.
Martin Blech
@Martin Blech. He añadido una pregunta, pero es bastante difícil de encajar lo tanto, yo soy un principiante por lo que han proporcionado tanta información como pueda, pero espero que pueda necesitar una mayor claridad stackoverflow.com/q/23676973/461887
sayth
Después de tanto tiempo, estoy un poco sorprendido de que xmltodict no sea una biblioteca "estándar" en algunas distribuciones de Linux. Aunque parece hacer el trabajo directamente de lo que podemos leer, desafortunadamente usaré otra solución como la conversión
xslt
Muchas gracias por escribir esta fantástica biblioteca. Aunque bs4puede hacer el trabajo de xml para dictar, es extremadamente fácil usar la biblioteca
Tessaracter
24

Puede usar la biblioteca xmljson para convertir usando diferentes convenciones JSON XML .

Por ejemplo, este XML:

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

se traduce a través de la convención BadgerFish en esto:

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

y a través de la convención GData en esto (los atributos no son compatibles):

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

... y a través de la convención de Parker en esto (los atributos no son compatibles):

{
  'p': 'text'
}

Es posible convertir de XML a JSON y de JSON a XML utilizando las mismas convenciones:

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

Divulgación: escribí esta biblioteca. Espero que ayude a los futuros buscadores.

S Anand
fuente
44
Esa es una biblioteca bastante buena, pero lea ¿Cómo ofrecer bibliotecas personales de código abierto? antes de publicar más respuestas para mostrarlo.
Martijn Pieters
1
Gracias @MartijnPieters - Acabo de pasar por esto y me aseguraré de seguir con esto.
S Anand
1
Gracias Anand por la solución: parece funcionar bien, no tiene dependencias externas y ofrece mucha flexibilidad en la forma en que se manejan los atributos utilizando las diferentes convenciones. Exactamente lo que necesitaba y fue la solución más flexible y simple que encontré.
mbbeme
Gracias Anand, desafortunadamente, no puedo hacer que analice XML con codificación utf8. Al pasar por las fuentes, parece que el conjunto de codificación a través de XMLParser (..) se ignora
Patrik Beck
@PatrikBeck ¿podría compartir un pequeño ejemplo de XML con codificación utf8 que se rompe?
S Anand
11

Si en algún momento obtienes solo el código de respuesta en lugar de todos los datos, entonces aparecerá un error como json parse , por lo que debes convertirlo como texto

import xmltodict

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

Aquí está el código que construí para eso. No hay análisis de los contenidos, solo conversión simple.

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

Hay un método para transportar el marcado basado en XML como JSON que le permite volver a convertirse sin pérdidas a su forma original. Ver http://jsonml.org/ .

Es una especie de XSLT de JSON. Espero que le sea útil

themihai
fuente
7

Para cualquiera que todavía pueda necesitar esto. Aquí hay un código más nuevo y simple para hacer esta conversión.

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
fuente
1
Funciona al menos en Python 3.7, aunque desafortunadamente agrega algunos datos inesperados a los nombres clave si ciertos valores están en su xml, por ejemplo, una etiqueta xmlns en un nodo de nivel raíz aparece en cada clave de nodo de esta manera: {'{ maven .apache.org / POM / 4.0.0 } artifactId ':' test-service ', que vino de xml de esta manera: <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

Es posible que desee echar un vistazo a http://designtheory.org/library/extrep/designdb-1.0.pdf . Este proyecto comienza con una conversión de XML a JSON de una gran biblioteca de archivos XML. Se realizó mucha investigación en la conversión, y se produjo el mapeo intuitivo más simple XML -> JSON (se describe al principio del documento). En resumen, convierta todo a un objeto JSON y coloque bloques repetidos como una lista de objetos.

objetos que significan pares clave / valor (diccionario en Python, hashmap en Java, objeto en JavaScript)

No hay una asignación a XML para obtener un documento idéntico, la razón es que se desconoce si un par clave / valor era un atributo o un <key>value</key>, por lo tanto, esa información se pierde.

Si me preguntas, los atributos son un truco para comenzar; luego, de nuevo, funcionaron bien para HTML.

pykler
fuente
4

Bueno, probablemente la forma más simple es analizar el XML en diccionarios y luego serializarlo con simplejson.

dguaraglia
fuente
4

Sugeriría no ir a una conversión directa. Convierta XML a un objeto, luego del objeto a JSON.

En mi opinión, esto da una definición más clara de cómo se corresponden el XML y JSON.

Lleva tiempo hacerlo bien e incluso puede escribir herramientas para ayudarlo a generar parte de él, pero se vería más o menos así:

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

Encontré recortes XML simples, el uso de expresiones regulares ahorraría problemas. Por ejemplo:

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

Para hacerlo mediante el análisis XML, como dijo @Dan, no existe una solución única porque los datos son diferentes. Mi sugerencia es usar lxml. Aunque no ha terminado con json, lxml.objectify da buenos resultados silenciosos:

>>> 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
fuente
1
pero elimina los nodos duplicados
Pooya
2

Si bien las bibliotecas incorporadas para el análisis XML son bastante buenas, soy parcial con lxml .

Pero para analizar las fuentes RSS, recomendaría Universal Feed Parser , que también puede analizar Atom. Su principal ventaja es que puede digerir incluso la mayoría de los alimentos malformados.

Python 2.6 ya incluye un analizador JSON, pero una versión más nueva con velocidad mejorada está disponible como simplejson .

Con estas herramientas, crear su aplicación no debería ser tan difícil.

Luka Marinko
fuente
2

Mi respuesta aborda el caso específico (y algo común) en el que realmente no necesita convertir todo el xml a json, pero lo que necesita es atravesar / acceder a partes específicas del xml, y necesita que sea rápido , y simple (usando operaciones json / dict-like).

Acercarse

Para esto, es importante tener en cuenta que analizar un xml para usar etree lxmles muy rápido. La parte lenta en la mayoría de las otras respuestas es el segundo paso: atravesar la estructura etree (generalmente en python-land), convertirla a json.

Lo que me lleva al enfoque que encontré mejor para este caso: analizar el xml usando lxml, y luego envolver los nodos etree (perezosamente), proporcionándoles una interfaz tipo dict.

Código

Aquí está el código:

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

Esta implementación no está completa, por ejemplo, no admite de manera clara los casos en que un elemento tiene texto y atributos, o texto y elementos secundarios (solo porque no lo necesitaba cuando lo escribí ...) Debería ser fácil para mejorarlo, sin embargo.

Velocidad

En mi caso de uso específico, donde necesitaba únicos elementos procesos específicos del xml, este enfoque dio un suprising y aceleración sorprendente en un factor de 70 (!) En comparación con el uso de @ Martin Blech xmltodict y luego atravesar el dict directamente.

Prima

Como beneficio adicional, dado que nuestra estructura ya es similar a un dict, obtenemos otra implementación alternativa de forma xml2jsongratuita. Solo necesitamos pasar nuestra estructura tipo dict a json.dumps. Algo como:

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

Si su xml incluye atributos, necesitaría usar algo alfanumérico attr_prefix(por ejemplo, "ATTR_"), para asegurarse de que las claves son claves json válidas.

No he evaluado esta parte.

shx2
fuente
Si intento hacerlo json.dumps(tree), dice que el objeto del tipo 'ETreeDictWrapper' no es serializable JSON
Vlad T.
2

Cuando hago algo con XML en Python, casi siempre uso el paquete lxml. Sospecho que la mayoría de la gente usa lxml. Podrías usar xmltodict pero tendrás que pagar la penalización de analizar el XML nuevamente.

Para convertir XML a json con lxml usted:

  1. Analizar documento XML con lxml
  2. Convierte lxml a un dict
  3. Convertir lista a json

Yo uso la siguiente clase en mis proyectos. Usa el método 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)

El resultado del main integrado es:

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

Cuál es una transformación de este xml:

<system>
    <product>
        <demod>
            <frequency value='2.215' units='MHz'>
                <blah value='1'/>
            </frequency>
        </demod>
    </product>
</system>
musaraña
fuente
1

echa un vistazo a lxml2json (divulgación: lo escribí)

https://github.com/rparelius/lxml2json

es muy rápido, liviano (solo requiere lxml), y una ventaja es que tiene control sobre si ciertos elementos se convierten en listas o dictados

Robert Parelius
fuente
1

Puedes usar declxml. Tiene características avanzadas como atributos múltiples y soporte anidado complejo. Solo necesita escribir un procesador simple para ello. También con el mismo código, también puede volver a convertir a JSON. Es bastante sencillo y la documentación es impresionante.

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

srth12
fuente
-1

Preparar datos en Python : para crear JSON primero debe preparar los datos en python. Podemos usar List and Dictionary en Python para preparar los datos.

Lista de Python <==> Matriz JSON

Diccionario Python <==> Objeto JSON (Formato de valor clave) Verifique esto para obtener más detalles

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

Anushree Anisha
fuente
¡Bienvenido a Stack Overflow! Si bien los enlaces son una excelente forma de compartir conocimientos, realmente no responderán la pregunta si se rompen en el futuro. Agregue a su respuesta el contenido esencial del enlace que responde a la pregunta. En caso de que el contenido sea demasiado complejo o demasiado grande para caber aquí, describa la idea general de la solución propuesta. Recuerde mantener siempre un enlace de referencia al sitio web de la solución original. Ver: ¿Cómo escribo una buena respuesta?
sɐunıɔ ןɐ qɐp
-4

Para representar datos en formato JSON

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

En json , repetimos datos en formato de clave y valor

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

Para representar datos en 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-->

Anushree Anisha
fuente