Преобразование XML в JSON с использованием Python?

114

Я видел справедливую долю неуклюжего XML- > кода JSON в Интернете и немного взаимодействовал с пользователями Stack, я убежден, что эта толпа может помочь больше, чем первые несколько страниц результатов Google..

Итак, мы анализируем погоду, и нам нужно заполнять виджеты погоды на множестве веб-сайтов. Мы сейчас рассматриваем решения на базе Python.

Этот общедоступный weather.com RSS-канал является хорошим примером того, что мы будем анализировать (наш фактический материал weather.com содержит дополнительную информацию из-за партнерства с ними). ​​

Вкратце, как нам преобразовать XML в JSON с помощью Python?

Теги:
converter

15 ответов

38
Лучший ответ

Нет никакого взаимно однозначного отображения между XML и JSON, поэтому для преобразования одного из них обязательно требуется некоторое понимание того, что вы хотите сделать с результатами.

При этом в стандартной библиотеке Python несколько модулей для анализа XML (включая DOM, SAX и ElementTree). Начиная с Python 2.6, поддержка преобразования структур данных Python в JSON и из него включена в модуль json.

Итак, инфраструктура существует.

  • 1
    xmljson IMHO является самым быстрым в использовании с поддержкой различных соглашений из коробки. pypi.org/project/xmljson
  • 0
    Это уже упоминалось в новых ответах. Он по-прежнему охватывает только небольшое подмножество допустимых конструкций XML, но, вероятно, большинство из того, что люди используют на практике.
214

xmltodict (полное раскрытие: я его написал) может помочь вам преобразовать ваш XML в структуру dict + list + string, следуя этому "standard" . Это Expat, поэтому он очень быстрый и не требует загрузки всего дерева XML в память.

Как только у вас будет такая структура данных, вы можете сериализовать ее в JSON:

import xmltodict, json

o = xmltodict.parse('<e> <a>text</a> <a>text</a> </e>')
json.dumps(o) # '{"e": {"a": ["text", "text"]}}'
  • 0
    @Martin Blech Если я создаю файл json из моего файла моделей django. Как я могу сопоставить мой XML-файл для преобразования XML в JSON для обязательных полей?
  • 1
    @ Sayth Я думаю, вы должны опубликовать это как отдельный вопрос SO.
Показать ещё 2 комментария
9

Вы можете использовать библиотеку xmljson для преобразования с использованием различных условных обозначений XML JSON.

Например, этот XML:

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

переводит через соглашение BadgerFish в это:

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

и через соглашение GData (атрибуты не поддерживаются):

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

... и через соглашение Parker (атрибуты не поддерживаются):

{
  'p': 'text'
}

Можно конвертировать из XML в JSON и из JSON в XML, используя тот же условные обозначения:

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

Раскрытие: я написал эту библиотеку. Надеюсь, это поможет будущим искателям.

  • 4
    Это довольно крутая библиотека, но, пожалуйста, прочитайте Как предложить персональные библиотеки с открытым исходным кодом? прежде чем публиковать больше ответов, показывающих это.
  • 1
    Спасибо @MartijnPieters - я только что прошел через это и буду следить за этим.
Показать ещё 3 комментария
5

Вот код, который я построил для этого. Там нет разбора содержимого, просто преобразования.

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

Существует метод переноса XML-разметки как JSON, который позволяет без потерь преобразовать его обратно в исходную форму. См. http://jsonml.org/.

Это своего рода XSLT JSON. Надеюсь, вы сочтете это полезным.

4

Вы можете посмотреть http://designtheory.org/library/extrep/designdb-1.0.pdf. Этот проект начинается с преобразования XML в JSON большой библиотеки XML файлов. В преобразовании было много исследований, и было создано самое простое интуитивное отображение XML → JSON (это описано в начале документа). В итоге преобразуем все в объект JSON и добавим повторяющиеся блоки в список объектов.

объекты, означающие пары ключ/значение (словарь в Python, hashmap в Java, объект в JavaScript)

Нет сопоставления с XML для получения идентичного документа, причина в том, что неизвестно, является ли пара ключ/значение атрибутом или <key>value</key>, поэтому эта информация теряется.

Если вы спросите меня, атрибуты - это взлом для запуска; то снова они хорошо работали для HTML.

3

Ну, наверное, самый простой способ - просто проанализировать XML в словарях, а затем сериализовать его с помощью simplejson.

2

Когда я что-то делаю с XML в python, я почти всегда использую пакет lxml. Я подозреваю, что большинство людей используют lxml. Вы можете использовать xmltodict, но вам придется заплатить штраф за разбор XML снова.

Чтобы преобразовать XML в json с помощью lxml, вы:

  • Анализ XML-документа с помощью lxml
  • Преобразование lxml в dict
  • Преобразовать список в json

В моих проектах я использую следующий класс. Используйте метод 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)

Выход из встроенного основного:

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

Что представляет собой преобразование этого xml:

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

Я предлагаю не собираться для прямого преобразования. Преобразуйте XML в объект, а затем из объекта в JSON.

По-моему, это дает более четкое определение того, как соответствуют XML и JSON.

Требуется время, чтобы получить право, и вы даже можете написать инструменты, которые помогут вам генерировать некоторые из них, но это будет выглядеть примерно так:

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 ):
    ...
2

Хотя встроенные библиотеки для синтаксического анализа XML довольно хороши, я частично отношусь к lxml.

Но для синтаксического анализа RSS-каналов я бы рекомендовал Universal Feed Parser, который также может анализировать Atom. Его главным преимуществом является то, что он может переваривать даже самые искаженные каналы.

Python 2.6 уже содержит парсер JSON, но более новая версия с улучшенной скоростью доступна как simplejson.

С помощью этих инструментов создание вашего приложения не должно быть таким сложным.

1

Тем, кому это все еще нужно. Здесь приведен новый, простой код для этого преобразования.

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
  • 0
    этот код не будет работать. пожалуйста отредактируйте и исправьте
1

Этот материал активно поддерживается и до сих пор является моим любимым: xml2json в python

1

В моем ответе указан конкретный (и довольно общий) случай, когда вам не нужно преобразовывать весь XML файл в json, но вам нужно пройти или получить доступ к определенным частям xml, и вам нужно быстро и просто (используя операции json/dict-like).

Подход

Для этого важно отметить, что синтаксический анализ xml до etree с помощью lxml очень быстрый. Медленная часть большинства других ответов - это второй проход: перемещение структуры etree (обычно на python-land), преобразование ее в json.

Это приводит меня к подходу, который я нашел лучше всего для этого случая: разбор XML-кода с помощью lxml, а затем обертывание узлов etree (лениво), предоставление им диктоподобного интерфейса.

код

Здесь код:

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

Эта реализация не является полной, например, она не обеспечивает чистоту случаев, когда элемент имеет как текст, так и атрибуты, или текст и дети (только потому, что я не нуждался в нем, когда писал)... должно быть легко улучшить его, тем не менее.

Speed ​​

В моем конкретном случае использования, когда мне нужно было обрабатывать только определенные элементы xml, этот подход дал удивительное ускорение и поразительное ускорение в 70 раз (!) по сравнению с использованием @Martin Blech xmltodict, а затем непосредственно перемещая dict.

Bonus

В качестве бонуса, поскольку наша структура уже диктоподобна, мы бесплатно получаем другую альтернативную реализацию xml2json. Нам просто нужно передать нашу диктоподобную структуру в json.dumps. Что-то вроде:

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

Если ваш xml включает атрибуты, вам нужно будет использовать несколько буквенно-цифровых attr_prefix (например, "ATTR_" ), чтобы гарантировать, что ключи являются действительными json-ключами.

Я не сравнивал эту часть.

1

Я нашел для простых XML-снимков, использование регулярных выражений спасло бы проблемы. Например:

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

Чтобы сделать это с помощью синтаксического анализа XML, как сказал @Dan, нет единого решения, потому что данные разные. Мое предложение - использовать lxml. Хотя не закончен json, lxml.objectify дают хорошие результаты:

>>> 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'
  • 1
    но он удаляет дубликаты узлов
1

jsonpickle, или если вы используете feedparser, вы можете попробовать feed_parser_to_json.py

Ещё вопросы

Сообщество Overcoder
Наверх
Меню