Konwersja XML do JSON przy użyciu Pythona?

170

Widziałem sporą część niezgrabnego kodu XML-> JSON w sieci i po dłuższej interakcji z użytkownikami Stacka jestem przekonany, że ten tłum może pomóc więcej niż kilka pierwszych stron wyników Google.

Dlatego analizujemy kanał pogodowy i musimy umieszczać widżety pogodowe w wielu witrynach internetowych. Zajmujemy się teraz rozwiązaniami opartymi na języku Python.

Ten publiczny kanał RSS weather.com jest dobrym przykładem tego, co będziemy analizować ( nasz aktualny kanał weather.com zawiera dodatkowe informacje z powodu partnerstwa z nimi ).

Krótko mówiąc, w jaki sposób powinniśmy przekonwertować XML na JSON za pomocą Pythona?

Pete Karl II
źródło

Odpowiedzi:

61

Nie ma mapowania „jeden do jednego” między XML a JSON, więc konwersja jednego do drugiego wymaga koniecznie zrozumienia, co chcesz zrobić z wynikami.

Biorąc to pod uwagę, standardowa biblioteka Pythona ma kilka modułów do analizowania XML (w tym DOM, SAX i ElementTree). Począwszy od Pythona 2.6, jsonmoduł zawiera obsługę konwersji struktur danych Python do iz formatu JSON .

Więc infrastruktura tam jest.

Dan Lenski
źródło
2
xmljson IMHO jest najszybszym w użyciu i obsługuje różne konwencje po wyjęciu z pudełka. pypi.org/project/xmljson
nitinr708
Wspomniano już o tym w nowszych odpowiedziach. Wciąż obejmuje tylko niewielki podzbiór prawidłowych konstrukcji XML, ale prawdopodobnie większość tego, czego ludzie używają w praktyce.
Dan Lenski,
281

xmltodict (pełne ujawnienie: napisałem to) może pomóc w konwersji twojego XML do struktury dict + lista + string, zgodnie z tym "standardem" . Jest oparty na Expat , więc jest bardzo szybki i nie wymaga ładowania całego drzewa XML do pamięci.

Gdy masz już tę strukturę danych, możesz serializować ją do formatu JSON:

import xmltodict, json

o = xmltodict.parse('<e> <a>text</a> <a>text</a> </e>')
json.dumps(o) # '{"e": {"a": ["text", "text"]}}'
Martin Blech
źródło
@Martin Blech Jeśli utworzę plik json z mojego pliku django models. Jak mogę zmapować mój plik xml, aby przekonwertować plik xml na json dla wymaganych pól?
powiedzmy
1
@sayth Myślę, że powinieneś opublikować to jako osobne pytanie SO.
Martin Blech
@Martin Blech. Dodałem pytanie, ale raczej ciężko jest zmieścić się w SO, jestem początkującym, więc
podałem
Po tak długim czasie jestem nieco zaskoczony, że xmltodict nie jest „standardową” biblioteką w niektórych dystrybucjach Linuksa. Choć wydaje się, że wykonuję swoją pracę prosto z tego, co czytamy, niestety
użyję
Wielkie dzięki za napisanie tej fantastycznej biblioteki. Chociaż bs4może wykonać zadanie xml, aby dyktować, że korzystanie z biblioteki jest niezwykle łatwe
Tessaracter
24

Możesz użyć biblioteki xmljson do konwersji przy użyciu różnych konwencji XML JSON .

Na przykład ten XML:

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

tłumaczy się poprzez konwencję BadgerFish na:

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

i poprzez konwencję GData do tego (atrybuty nie są obsługiwane):

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

... i poprzez konwencję Parkera do tego (atrybuty nie są obsługiwane):

{
  'p': 'text'
}

Możliwe jest przekonwertowanie z XML na JSON iz JSON na XML przy użyciu tych samych konwencji:

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

Ujawnienie: napisałem tę bibliotekę. Mam nadzieję, że pomoże to przyszłym użytkownikom.

S Anand
źródło
4
To całkiem fajna biblioteka, ale przeczytaj proszę Jak oferować osobiste biblioteki open source? zanim opublikujesz więcej odpowiedzi, aby to pokazać.
Martijn Pieters
1
Dzięki @MartijnPieters - właśnie przez to przeszedłem i upewnię się, że się tego trzymam.
S Anand,
1
Dzięki Anand za rozwiązanie - wydaje się, że działa dobrze, nie ma zewnętrznych zależności i zapewnia dużą elastyczność w sposobie obsługi atrybutów przy użyciu różnych konwencji. Dokładnie to, czego potrzebowałem i było najbardziej elastycznym i najprostszym rozwiązaniem, jakie znalazłem.
mbbeme
Dzięki Anand - niestety nie mogę go zmusić do przeanalizowania XML z kodowaniem utf8. Przechodząc przez źródła, wydaje się, że kodowanie ustawione przez XMLParser (..) jest ignorowane
Patrik Beck
@PatrikBeck, czy mógłbyś podzielić się małym przykładem XML z kodowaniem utf8, które się psuje?
S Anand
11

Jeśli kiedyś otrzymasz tylko kod odpowiedzi zamiast wszystkich danych, pojawi się błąd, taki jak json parse , więc musisz przekonwertować go na tekst

import xmltodict

data = requests.get(url)
xpars = xmltodict.parse(data.text)
json = json.dumps(xpars)
print json 
Akshay Kumbhar
źródło
7

Oto kod, który do tego stworzyłem. Nie ma analizy zawartości, po prostu zwykła konwersja.

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
źródło
7

Istnieje metoda transportu znaczników opartych na XML jako JSON, która umożliwia bezstratną konwersję z powrotem do oryginalnej postaci. Zobacz http://jsonml.org/ .

To rodzaj XSLT w formacie JSON. Mam nadzieję, że będzie to dla ciebie pomocne

themihai
źródło
7

Dla każdego, kto może nadal tego potrzebować. Oto nowszy, prosty kod do wykonania tej konwersji.

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
źródło
1
Działa przynajmniej w Pythonie 3.7, ale niestety dodaje pewne nieoczekiwane dane do nazw kluczy, jeśli pewne wartości znajdują się w twoim xml, na przykład tag xmlns na węźle poziomu głównego pojawia się w każdym kluczu węzła w ten sposób: {'{ maven .apache.org / POM / 4.0.0 } artifactId ':' test-service ', który pochodzi z xml w ten sposób: <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

Możesz zajrzeć na http://designtheory.org/library/extrep/designdb-1.0.pdf . Ten projekt zaczyna się od konwersji XML do JSON dużej biblioteki plików XML. Przeprowadzono wiele badań nad konwersją i stworzono najprostsze intuicyjne mapowanie XML -> JSON (zostało to opisane na początku dokumentu). Podsumowując, przekonwertuj wszystko na obiekt JSON i umieść powtarzające się bloki jako listę obiektów.

obiekty oznaczające pary klucz / wartość (słownik w Pythonie, hashmap w Javie, obiekt w JavaScript)

Nie ma odwzorowania z powrotem na XML, aby uzyskać identyczny dokument, powodem jest to, że nie wiadomo, czy para klucz / wartość była atrybutem, czy też atrybutem <key>value</key>, dlatego informacje te są tracone.

Jeśli o mnie chodzi, atrybuty to dobry początek; potem znowu działały dobrze dla HTML.

pykler
źródło
4

Cóż, prawdopodobnie najprostszym sposobem jest po prostu przeanalizowanie XML do słowników, a następnie serializacja za pomocą simplejson.

dguaraglia
źródło
4

Proponuję nie przechodzić na bezpośrednią konwersję. Przekonwertuj XML na obiekt, a następnie z obiektu na JSON.

Moim zdaniem daje to bardziej przejrzystą definicję tego, jak korespondują XML i JSON.

Potrzeba czasu, aby wszystko naprawić i możesz nawet napisać narzędzia, które pomogą Ci wygenerować część z nich, ale wyglądałoby to mniej więcej tak:

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
źródło
2

Znalazłem dla prostych wycinków XML, użycie wyrażenia regularnego pozwoliłoby uniknąć problemów. Na przykład:

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

Aby to zrobić za pomocą analizowania XML, jak powiedział @Dan, nie ma rozwiązania jednego dla wszystkich, ponieważ dane są różne. Proponuję użyć lxml. Chociaż nie jest gotowy do json, lxml.objectify daje ciche dobre wyniki:

>>> 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
źródło
1
ale usuwa zduplikowane węzły
Pooya
2

Chociaż wbudowane biblioteki do analizowania XML są całkiem dobre, jestem zwolennikiem lxml .

Ale do analizowania kanałów RSS polecam Universal Feed Parser , który może również analizować Atom. Jego główną zaletą jest to, że jest w stanie strawić nawet większość zniekształconych pasz.

Python 2.6 już zawiera parser JSON, ale nowsza wersja o zwiększonej szybkości jest dostępna jako simplejson .

Dzięki tym narzędziom tworzenie aplikacji nie powinno być takie trudne.

Luka Marinko
źródło
2

Moja odpowiedź dotyczy konkretnego (i dość powszechnego) przypadku, w którym tak naprawdę nie musisz konwertować całego pliku xml na json, ale potrzebujesz przejść / uzyskać dostęp do określonych części xml i potrzebujesz, aby był szybki , i proste (przy użyciu operacji podobnych do json / dict).

Podejście

W tym celu należy zauważyć, że parsowanie XML do etree przy użyciu lxmljest bardzo szybkie. Powolną częścią większości innych odpowiedzi jest drugie przejście: przejście struktury etree (zwykle w python-land), konwersja do json.

Co prowadzi mnie do podejścia, które znalazłem najlepsze w tym przypadku: analizowanie XML za pomocą lxml, a następnie owijanie węzłów etree (leniwie), zapewniając im interfejs podobny do dyktu.

Kod

Oto kod:

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

Ta implementacja nie jest kompletna, np. Nie obsługuje czysto przypadków, w których element ma zarówno tekst, jak i atrybuty, lub zarówno tekst, jak i dzieci (tylko dlatego, że nie potrzebowałem go, kiedy go pisałem ...) Powinno być łatwe żeby go jednak ulepszyć.

Prędkość

W moim konkretnym przypadku użycia, w którym musiałem przetworzyć tylko określone elementy XML, to podejście dało zaskakujące i uderzające przyspieszenie o współczynnik 70 (!) W porównaniu z użyciem xmltodict @ Martina Blecha, a następnie przechodząc bezpośrednio przez dyktę .

Premia

Jako bonus, ponieważ nasza struktura jest już dyktowana, otrzymujemy kolejną alternatywną implementację xml2jsonza darmo. Musimy tylko przekazać naszą strukturę podobną do dyktowania json.dumps. Coś jak:

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

Jeśli Twój xml zawiera atrybuty, musisz użyć pewnych attr_prefixznaków alfanumerycznych (np. „ATTR_”), aby upewnić się, że klucze są prawidłowymi kluczami json.

Nie testowałem tej części.

shx2
źródło
Jeśli spróbuję json.dumps(tree)to zrobić , mówi, że obiekt typu „ETreeDictWrapper” nie jest możliwy do serializacji w formacie JSON
Vlad T.,
2

Kiedy robię cokolwiek z XML w Pythonie, prawie zawsze używam pakietu lxml. Podejrzewam, że większość ludzi używa lxml. Możesz użyć xmltodict, ale będziesz musiał zapłacić karę za ponowne przeanalizowanie XML.

Aby przekonwertować XML na json za pomocą lxml, należy:

  1. Przeanalizuj dokument XML za pomocą lxml
  2. Konwertuj lxml na dict
  3. Konwertuj listę na json

W swoich projektach używam następującej klasy. Użyj metody 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)

Dane wyjściowe z wbudowanego main to:

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

Co jest transformacją tego XML:

<system>
    <product>
        <demod>
            <frequency value='2.215' units='MHz'>
                <blah value='1'/>
            </frequency>
        </demod>
    </product>
</system>
złośnica
źródło
1

sprawdź lxml2json (ujawnienie: napisałem)

https://github.com/rparelius/lxml2json

jest bardzo szybki, lekki (wymaga tylko lxml), a jedną z zalet jest to, że masz kontrolę nad tym, czy niektóre elementy są konwertowane na listy lub dykty

Robert Parelius
źródło
1

Możesz użyć declxml. Posiada zaawansowane funkcje, takie jak wiele atrybutów i złożona obsługa zagnieżdżonych. Wystarczy napisać do niego prosty procesor. Również z tym samym kodem możesz przekonwertować z powrotem na JSON. Jest to dość proste, a dokumentacja jest niesamowita.

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

srth12
źródło
-1

Przygotuj dane w Pythonie : Aby utworzyć JSON , musisz najpierw przygotować dane w Pythonie. Do przygotowania danych możemy wykorzystać List i Dictionary w Pythonie.

Lista Pythona <==> Tablica JSON

Słownik Pythona <==> Obiekt JSON (format wartości klucza) Zaznacz, aby uzyskać więcej informacji

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

Anushree Anisha
źródło
Witamy w Stack Overflow! Chociaż linki są świetnym sposobem na dzielenie się wiedzą, tak naprawdę nie odpowiedzą na pytanie, jeśli w przyszłości zostaną zerwane. Dodaj do swojej odpowiedzi zasadniczą treść linku, który odpowiada na pytanie. W przypadku, gdy treść jest zbyt złożona lub zbyt duża, aby się tutaj zmieścić, opisz ogólną ideę proponowanego rozwiązania. Pamiętaj, aby zawsze zachować odsyłacz do strony internetowej oryginalnego rozwiązania. Zobacz: Jak napisać dobrą odpowiedź?
sɐunıɔ ןɐ qɐp
-4

Aby przedstawić dane w formacie JSON

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

W json reprezentujemy dane w formacie klucza i wartości

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

Aby przedstawić dane w formacie 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
źródło