Convertendo XML para JSON usando Python?

170

Eu já vi uma parte justa do código XML-> JSON desagradável na web e, depois de interagir um pouco com os usuários do Stack, estou convencido de que essa multidão pode ajudar mais do que as primeiras páginas dos resultados do Google.

Portanto, estamos analisando um feed de previsão do tempo e precisamos preencher widgets climáticos em vários sites. Estamos analisando agora as soluções baseadas em Python.

Este feed RSS público do weather.com é um bom exemplo do que estaríamos analisando ( nosso feed real do weather.com contém informações adicionais devido a uma parceria com eles ).

Em poucas palavras, como devemos converter XML para JSON usando Python?

Pete Karl II
fonte

Respostas:

61

Não existe um mapeamento "um para um" entre XML e JSON, portanto, a conversão de um para o outro requer necessariamente alguma compreensão do que você deseja fazer com os resultados.

Dito isto, a biblioteca padrão do Python possui vários módulos para analisar XML (incluindo DOM, SAX e ElementTree). A partir do Python 2.6, o suporte à conversão de estruturas de dados Python de e para JSON está incluído no jsonmódulo .

Então a infraestrutura está lá.

Dan Lenski
fonte
2
O xmljson IMHO é o mais rápido de usar, com suporte para várias convenções prontas para uso. pypi.org/project/xmljson
nitinr708
Já foi mencionado em respostas mais recentes. Ele ainda cobre apenas um pequeno subconjunto de construções XML válidas, mas provavelmente a maioria do que as pessoas usam na prática.
quer
281

O xmltodict (divulgação completa: escrevi) pode ajudá-lo a converter seu XML em uma estrutura dict + list + string, seguindo este "padrão" . É Expat baseado em , portanto, é muito rápido e não precisa carregar toda a árvore XML na memória.

Depois de ter essa estrutura de dados, você pode serializá-la para JSON:

import xmltodict, json

o = xmltodict.parse('<e> <a>text</a> <a>text</a> </e>')
json.dumps(o) # '{"e": {"a": ["text", "text"]}}'
Martin Blech
fonte
@ Martin Blech Se eu criar um arquivo json a partir do meu arquivo de modelos django. Como posso mapear meu arquivo xml para converter o xml em json para os campos obrigatórios?
sayth
1
@ Sayth Eu acho que você deve postar isso como uma pergunta SO separada.
Martin Blech
@Martin Blech. Eu adicionei uma pergunta, mas é um pouco difícil para caber no SO, eu sou um novato por isso deram tanta informação quanto eu posso, mas eu espero que você pode exigir mais clareza stackoverflow.com/q/23676973/461887
sayth
Depois de tanto tempo, estou um pouco surpreso xmltodict não é uma biblioteca "padrão" em algumas distribuições linux. Retomando embora parece fazer o trabalho direto do que podemos ler, eu, infelizmente, usar outra solução como a conversão XSLT
sancelot
Muito obrigado por escrever esta fantástica biblioteca. Embora bs4pode fazer o trabalho de xml para dict é extremamente fácil de usar a biblioteca
Tessaracter
24

Você pode usar a biblioteca xmljson para converter usando diferentes convenções XML JSON .

Por exemplo, este XML:

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

traduz através da convenção BadgerFish para isso:

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

e através da convenção GData para isso (atributos não são suportados):

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

... e através da convenção da Parker para isso (atributos não são suportados):

{
  'p': 'text'
}

É possível converter de XML para JSON e de JSON para XML usando as mesmas convenções:

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

Divulgação: escrevi esta biblioteca. Espero que ajude os futuros pesquisadores.

S Anand
fonte
4
Essa é uma biblioteca muito legal, mas leia Como oferecer bibliotecas pessoais de código aberto? antes de postar mais respostas mostrando isso.
Martijn Pieters
1
Obrigado @MartijnPieters - Acabei de passar por isso e me certificarei de manter isso.
S Anand
1
Agradecemos a Anand pela solução - ela parece funcionar bem, não possui dependências externas e oferece muita flexibilidade na maneira como os atributos são manipulados usando as diferentes convenções. Exatamente o que eu precisava e foi a solução mais flexível e mais simples que encontrei.
mbbeme
Obrigado Anand - infelizmente, não consigo analisá-lo com a codificação utf8. Passando por fontes, parece que o conjunto de codificação através XMLParser (..) é ignorado
Patrik Beck
@PatrikBeck, você poderia compartilhar um pequeno exemplo de XML com a codificação utf8 que quebra?
S Anand
11

Se algum tempo você obtiver apenas o código de resposta em vez de todos os dados, ocorrerá um erro como o json parse , portanto você precisará convertê-lo como texto

import xmltodict

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

Aqui está o código que eu criei para isso. Não há análise do conteúdo, apenas conversão simples.

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

Existe um método para transportar a marcação baseada em XML como JSON, que permite que ela seja convertida sem perdas de volta à sua forma original. Veja http://jsonml.org/ .

É um tipo de XSLT de JSON. Espero que você ache isso útil

themihai
fonte
7

Para qualquer um que ainda precise disso. Aqui está um código simples e mais recente para fazer essa conversão.

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
fonte
1
Ele funciona no Python 3.7, pelo menos, embora, infelizmente, adicione alguns dados inesperados aos nomes das chaves se determinados valores estiverem no seu xml, por exemplo, uma tag xmlns em um nó no nível raiz aparece em cada chave do nó como esta: {'{ maven .apache.org / POM / 4.0.0 } artifactId ':' test-service ', que veio do xml assim: <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

Você pode dar uma olhada em http://designtheory.org/library/extrep/designdb-1.0.pdf . Este projeto começa com uma conversão XML para JSON de uma grande biblioteca de arquivos XML. Houve muita pesquisa realizada na conversão, e o mapeamento XML -> JSON intuitivo mais simples foi produzido (descrito no início do documento). Em resumo, converta tudo em um objeto JSON e coloque blocos repetidos como uma lista de objetos.

objetos que significam pares de chave / valor (dicionário em Python, mapa de hash em Java, objeto em JavaScript)

Não há mapeamento de volta para XML para obter um documento idêntico; o motivo é que não se sabe se um par de chave / valor era um atributo ou um <key>value</key> ; portanto, essas informações são perdidas.

Se você me perguntar, os atributos são um truque para começar; então, novamente, eles funcionaram bem para HTML.

pykler
fonte
4

Bem, provavelmente a maneira mais simples é apenas analisar o XML em dicionários e serializá-lo com o simplejson.

dguaraglia
fonte
4

Eu sugiro não optar por uma conversão direta. Converta XML em um objeto e, em seguida, do objeto em JSON.

Na minha opinião, isso fornece uma definição mais clara de como o XML e o JSON correspondem.

Leva tempo para acertar e você pode até escrever ferramentas para ajudá-lo a gerar parte disso, mas seria mais ou menos assim:

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

Eu encontrei para snips XML simples, o uso de expressão regular salvaria problemas. Por exemplo:

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

Para fazer isso através da análise de XML, como disse @Dan, não existe uma solução completa porque os dados são diferentes. Minha sugestão é usar o lxml. Embora não tenha concluído o json, o lxml.objectify fornece bons 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
fonte
1
mas remove nós duplicados
Pooya
2

Enquanto as bibliotecas internas para análise de XML são muito boas, sou parcial com o lxml .

Mas, para analisar feeds RSS, recomendo o Universal Feed Parser , que também pode analisar o Atom. Sua principal vantagem é que ele pode digerir até a maioria dos alimentos malformados.

O Python 2.6 já inclui um analisador JSON, mas uma versão mais recente com velocidade aprimorada está disponível como simplejson .

Com essas ferramentas, o aplicativo não deve ser tão difícil.

Luka Marinko
fonte
2

Minha resposta aborda o caso específico (e um tanto comum) em que você realmente não precisa converter o xml inteiro em json, mas o que você precisa é atravessar / acessar partes específicas do xml, e você precisa ser rápido , e simples (usando operações do tipo json / dict).

Abordagem

Para isso, é importante observar que a análise de um xml para etree usando lxmlé super rápida. A parte lenta na maioria das outras respostas é a segunda passagem: atravessando a estrutura etree (geralmente em python-land), convertendo-a em json.

O que me leva à abordagem que achei melhor para este caso: analisar o xml usando lxmle, em seguida, agrupar os nós etree (preguiçosamente), fornecendo a eles uma interface semelhante a um ditado.

Código

Aqui está o 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),
    )

Essa implementação não está completa, por exemplo, não oferece suporte a casos em que um elemento possui texto e atributos, ou texto e filhos (apenas porque eu não precisei quando o escrevi ...) para melhorá-lo, no entanto.

Rapidez

No meu caso de uso específico, onde eu precisava processar apenas elementos específicos do xml, essa abordagem deu uma surpreendente e aceleração impressionante por um fator de 70 (!) Em relação ao uso do @ Martin Blech xmltodict e depois atravessando o dict diretamente.

Bônus

Como bônus, como nossa estrutura já é semelhante a um ditado, obtemos outra implementação alternativa de xml2jsongraça. Só precisamos passar nossa estrutura semelhante a um ditado parajson.dumps . Algo como:

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

Se o seu xml incluir atributos, você precisará usar alguns caracteres alfanuméricos attr_prefix (por exemplo, "ATTR_"), para garantir que as chaves sejam chaves json válidas.

Eu não comparei essa parte.

shx2
fonte
Se eu tentar fazer json.dumps(tree)ele diz Objeto do tipo 'ETreeDictWrapper' não é JSON serializado
Vlad T.
2

Quando faço qualquer coisa com XML em python, quase sempre uso o pacote lxml. Eu suspeito que a maioria das pessoas usa lxml. Você pode usar xmltodict, mas terá que pagar a penalidade de analisar o XML novamente.

Para converter XML para json com lxml, você:

  1. Analisar documento XML com lxml
  2. Converter lxml em um dict
  3. Converter lista em json

Eu uso a seguinte classe em meus projetos. Use o 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)

A saída do main embutido é:

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

O que é uma transformação desse xml:

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

Esse material aqui é mantido ativamente e até agora é o meu favorito: xml2json in python

truthadjustr
fonte
1

confira lxml2json (divulgação: eu escrevi)

https://github.com/rparelius/lxml2json

é muito rápido, leve (requer apenas lxml) e uma vantagem é que você tem controle sobre a conversão de determinados elementos em listas ou dictos

Robert Parelius
fonte
1

Você pode usar declxml. Possui recursos avançados, como vários atributos e suporte aninhado complexo. Você só precisa escrever um processador simples para isso. Também com o mesmo código, você pode converter novamente em JSON. É bastante simples e a documentação é impressionante.

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

srth12
fonte
-1

Prepare dados em Python : Para criar JSON primeiro, você precisa preparar os dados no python. Podemos usar Lista e Dicionário em Python para preparar os dados.

Lista Python <==> Matriz JSON

Dicionário Python <==> Objeto JSON (formato do valor da chave) Verifique isso para obter mais detalhes

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

Anushree Anisha
fonte
Bem-vindo ao Stack Overflow! Embora os links sejam uma ótima maneira de compartilhar conhecimento, eles realmente não responderão à pergunta se forem quebrados no futuro. Adicione à sua resposta o conteúdo essencial do link que responde à pergunta. Caso o conteúdo seja muito complexo ou grande demais para caber aqui, descreva a ideia geral da solução proposta. Lembre-se de manter sempre uma referência de link para o site da solução original. Veja: Como escrevo uma boa resposta?
sɐunıɔ ןɐ qɐp
-4

Para representar dados no formato JSON

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

Em json , representamos os dados no formato de chave e 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 dados no 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
fonte