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

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

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

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

В двух словах, как мы должны конвертировать XML в JSON, используя Python?

23 ответа

Решение

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

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

Так что инфраструктура есть.

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

Получив эту структуру данных, вы можете сериализовать ее в JSON:

import xmltodict, json

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

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

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

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

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

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

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

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

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

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

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

Если через какое-то время вы получите только код ответа вместо всех данных, то появится ошибка типа json parse, поэтому вам нужно преобразовать ее в текст

import xmltodict

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

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

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

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

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

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

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

Я бы посоветовал не идти на прямое обращение. Преобразуйте 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 ):
    ...

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

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

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

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

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

Чтобы преобразовать XML в JSON с помощью LXML вы:

  1. Разбор XML-документа с помощью lxml
  2. Конвертировать lxml в dict
  3. Конвертировать список в 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>

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

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

Подход

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

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

Код

Вот код:

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

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

скорость

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

бонус

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

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

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

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

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

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

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

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

Я нашел для простых фрагментов 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. http://lxml.de/objectify.html, хотя и не завершено для json, дает хорошие результаты:

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

Проверить lxml2json (раскрытие: я написал это)

https://github.com/rparelius/lxml2json

это очень быстро, легко (требуется только lxml), и одним из преимуществ является то, что вы можете контролировать, будут ли определенные элементы преобразованы в списки или в диктовки

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

Вы можете использовать declxml. Он имеет расширенные функции, такие как множественные атрибуты и сложную вложенную поддержку Вам просто нужно написать простой процессор для него. Также с тем же кодом вы можете конвертировать обратно в JSON. Это довольно просто и документация потрясающая.

Ссылка: https://declxml.readthedocs.io/en/latest/index.html

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

Если вы не хотите использовать какие-либо внешние библиотеки и сторонние инструменты, попробуйте следующий код.

Код

import re
import json

def getdict(content):
    res=re.findall("<(?P<var>\S*)(?P<attr>[^/>]*)(?:(?:>(?P<val>.*?)</(?P=var)>)|(?:/>))",content)
    if len(res)>=1:
        attreg="(?P<avr>\S+?)(?:(?:=(?P<quote>['\"])(?P<avl>.*?)(?P=quote))|(?:=(?P<avl1>.*?)(?:\s|$))|(?P<avl2>[\s]+)|$)"
        if len(res)>1:
            return [{i[0]:[{"@attributes":[{j[0]:(j[2] or j[3] or j[4])} for j in re.findall(attreg,i[1].strip())]},{"$values":getdict(i[2])}]} for i in res]
        else:
            return {res[0]:[{"@attributes":[{j[0]:(j[2] or j[3] or j[4])} for j in re.findall(attreg,res[1].strip())]},{"$values":getdict(res[2])}]}
    else:
        return content

with open("test.xml","r") as f:
    print(json.dumps(getdict(f.read().replace('\n',''))))

Пример ввода

<details class="4b" count=1 boy>
    <name type="firstname">John</name>
    <age>13</age>
    <hobby>Coin collection</hobby>
    <hobby>Stamp collection</hobby>
    <address>
        <country>USA</country>
        <state>CA</state>
    </address>
</details>
<details empty="True"/>
<details/>
<details class="4a" count=2 girl>
    <name type="firstname">Samantha</name>
    <age>13</age>
    <hobby>Fishing</hobby>
    <hobby>Chess</hobby>
    <address current="no">
        <country>Australia</country>
        <state>NSW</state>
    </address>
</details>

Выход

[
  {
    "details": [
      {
        "@attributes": [
          {
            "class": "4b"
          },
          {
            "count": "1"
          },
          {
            "boy": ""
          }
        ]
      },
      {
        "$values": [
          {
            "name": [
              {
                "@attributes": [
                  {
                    "type": "firstname"
                  }
                ]
              },
              {
                "$values": "John"
              }
            ]
          },
          {
            "age": [
              {
                "@attributes": []
              },
              {
                "$values": "13"
              }
            ]
          },
          {
            "hobby": [
              {
                "@attributes": []
              },
              {
                "$values": "Coin collection"
              }
            ]
          },
          {
            "hobby": [
              {
                "@attributes": []
              },
              {
                "$values": "Stamp collection"
              }
            ]
          },
          {
            "address": [
              {
                "@attributes": []
              },
              {
                "$values": [
                  {
                    "country": [
                      {
                        "@attributes": []
                      },
                      {
                        "$values": "USA"
                      }
                    ]
                  },
                  {
                    "state": [
                      {
                        "@attributes": []
                      },
                      {
                        "$values": "CA"
                      }
                    ]
                  }
                ]
              }
            ]
          }
        ]
      }
    ]
  },
  {
    "details": [
      {
        "@attributes": [
          {
            "empty": "True"
          }
        ]
      },
      {
        "$values": ""
      }
    ]
  },
  {
    "details": [
      {
        "@attributes": []
      },
      {
        "$values": ""
      }
    ]
  },
  {
    "details": [
      {
        "@attributes": [
          {
            "class": "4a"
          },
          {
            "count": "2"
          },
          {
            "girl": ""
          }
        ]
      },
      {
        "$values": [
          {
            "name": [
              {
                "@attributes": [
                  {
                    "type": "firstname"
                  }
                ]
              },
              {
                "$values": "Samantha"
              }
            ]
          },
          {
            "age": [
              {
                "@attributes": []
              },
              {
                "$values": "13"
              }
            ]
          },
          {
            "hobby": [
              {
                "@attributes": []
              },
              {
                "$values": "Fishing"
              }
            ]
          },
          {
            "hobby": [
              {
                "@attributes": []
              },
              {
                "$values": "Chess"
              }
            ]
          },
          {
            "address": [
              {
                "@attributes": [
                  {
                    "current": "no"
                  }
                ]
              },
              {
                "$values": [
                  {
                    "country": [
                      {
                        "@attributes": []
                      },
                      {
                        "$values": "Australia"
                      }
                    ]
                  },
                  {
                    "state": [
                      {
                        "@attributes": []
                      },
                      {
                        "$values": "NSW"
                      }
                    ]
                  }
                ]
              }
            ]
          }
        ]
      }
    ]
  }
]

Некоторое время назад я опубликовал один на github ..

https://github.com/davlee1972/xml_to_json

Этот конвертер написан на Python и преобразует один или несколько файлов XML в файлы JSON / JSONL.

Для определения вложенных структур json (словари и списки) и эквивалентных типов данных json требуется файл схемы XSD.

      python xml_to_json.py -x PurchaseOrder.xsd PurchaseOrder.xml

INFO - 2018-03-20 11:10:24 - Parsing XML Files..
INFO - 2018-03-20 11:10:24 - Processing 1 files
INFO - 2018-03-20 11:10:24 - Parsing files in the following order:
INFO - 2018-03-20 11:10:24 - ['PurchaseOrder.xml']
DEBUG - 2018-03-20 11:10:24 - Generating schema from PurchaseOrder.xsd
DEBUG - 2018-03-20 11:10:24 - Parsing PurchaseOrder.xml
DEBUG - 2018-03-20 11:10:24 - Writing to file PurchaseOrder.json
DEBUG - 2018-03-20 11:10:24 - Completed PurchaseOrder.xml

У меня также есть последующий конвертер xml в паркет, который работает аналогичным образом

https://github.com/blackrock/xml_to_parquet

Если вы используете Python, вы можете проанализировать этот результат JSON из результата XML.
Но ваш результат SOAP должен быть выходом XML, вы можете использовать retxml=True на sudsбиблиотека.
Мне нужен был этот результат, и я решил так:

      import xmltodict

# Parse the XML result into dict
data_dict = xmltodict.parse(soap_response)

# Dump the dict result into a JSON result
json_data = json.dumps(data_dict)
    
# Load the JSON string result
json = json.loads(json_data)

Подготовка данных в Python: Для создания JSON сначала вам необходимо подготовить данные в Python. Мы можем использовать List и Dictionary в Python для подготовки данных.

Список Python <==> Массив JSON

Словарь Python <==> Объект JSON (формат значения ключа) Проверьте это для более подробной информации

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

Представлять данные в формате JSON

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

В json мы представляем данные в формате ключа и значения

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

Представлять данные в формате 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-->

Другие вопросы по тегам