dict python json xml converter

dict - json to xml python



¿Convirtiendo XML a JSON usando Python? (16)

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

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

He visto una buena cantidad de desgarbado código XML-> JSON en la web, y habiendo 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 un feed meteorológico, y necesitamos poblar widgets meteorológicos en una multitud de sitios web. Ahora estamos buscando soluciones basadas en Python.

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

En pocas palabras, ¿cómo debemos convertir XML a JSON utilizando Python?


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


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

Para convertir XML a json con lxml usted:

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

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)

La salida del principal integrado es:

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

Que es una transformación de este xml:

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


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 hay una solución única porque los datos son diferentes. Mi sugerencia es usar lxml. Aunque no está terminado para json, lxml.objectify da buenos resultados:

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


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 realizaron muchas investigaciones en la conversión, y se produjo el mapeo XML -> JSON intuitivo más simple (se describe al principio del documento). En resumen, convierta todo en un objeto JSON y coloque bloques repetidos como una lista de objetos.

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

No hay una correlación con XML para obtener un documento idéntico, la razón es que no se sabe si un par de 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 nuevamente funcionaron bien para HTML.



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

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


Mi respuesta aborda el caso específico (y algo común) en el que realmente no necesita convertir el xml completo 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).

Enfoque

Para esto, es importante tener en cuenta que analizar un xml a etree usando lxml es súper rápido. La parte lenta en la mayoría de las otras respuestas es la segunda: atraviesa la estructura etree (usualmente en python-land), convirtiéndola en 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 similar a 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 es completa, por ejemplo, no admite casos donde un elemento tiene tanto texto como 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 procesar únicamente elementos específicos del xml, este enfoque dio un sorprendente y asombroso aumento de velocidad por un factor de 70 (!) Comparado con el uso del xmltodict de @Martin Blech y luego atravesar el dict directamente.

Prima

Como extra, dado que nuestra estructura ya es similar a xml2json , obtenemos otra implementación alternativa de xml2json de forma gratuita. Solo tenemos que pasar nuestra estructura parecida a json.dumps 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á usar algún attr_prefix alfanumérico (por ejemplo, "ATTR_"), para garantizar que las claves sean claves json válidas.

No he comparado esta parte.


No existe un mapeo "uno a uno" entre XML y JSON, por lo que para convertir uno en otro necesariamente se necesita una cierta comprensión de lo que se quiere 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 soporte para la conversión de estructuras de datos de Python hacia y desde JSON está incluido en el módulo json .

Entonces la infraestructura está ahí.


Para cualquier persona que aún 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


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

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 mediante 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 buscadores futuros.


Si bien las lxml integradas para el análisis de XML son bastante buenas, soy parcial a lxml .

Pero para analizar feeds RSS, recomiendo 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, construir su aplicación no debería ser tan difícil.


Si en algún momento obtienes solo el código de respuesta en lugar de todos los datos, entonces habrá un error como json parse , así que deberás convertirlo como texto

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


Sugeriría que no vayas a una conversión directa. Convierta XML a un objeto, luego desde el objeto a JSON.

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

Se necesita tiempo para hacerlo bien e incluso puede escribir herramientas para ayudarlo a generar algo, 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 ): ...



xmltodict (divulgación completa: lo escribí) puede ayudarte a convertir tu XML a una estructura de cadena dict + list +, siguiendo este "standard" . 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 en JSON:

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