Преобразование 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 вы:
- Разбор 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>
Ну, наверное, самый простой способ - просто разобрать 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. Это довольно просто и документация потрясающая.
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 в паркет, который работает аналогичным образом
Если вы используете 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-->