Xpath-подобный запрос для вложенных словарей Python
Есть ли способ определить запрос типа XPath для вложенных словарей Python.
Что-то вроде этого:
foo = {
'spam':'eggs',
'morefoo': {
'bar':'soap',
'morebar': {'bacon' : 'foobar'}
}
}
print( foo.select("/morefoo/morebar") )
>> {'bacon' : 'foobar'}
Мне также нужно было выбрать вложенные списки;)
Это можно легко сделать с помощью решения @jellybean:
def xpath_get(mydict, path):
elem = mydict
try:
for x in path.strip("/").split("/"):
try:
x = int(x)
elem = elem[x]
except ValueError:
elem = elem.get(x)
except:
pass
return elem
foo = {
'spam':'eggs',
'morefoo': [{
'bar':'soap',
'morebar': {
'bacon' : {
'bla':'balbla'
}
}
},
'bla'
]
}
print xpath_get(foo, "/morefoo/0/morebar/bacon")
[РЕДАКТИРОВАТЬ 2016] Этот вопрос и принятый ответ являются древними. Более новые ответы могут сделать работу лучше, чем оригинальный ответ. Однако я их не проверял, поэтому не буду менять принятый ответ.
11 ответов
Не совсем красиво, но вы можете использовать что-то вроде
def xpath_get(mydict, path):
elem = mydict
try:
for x in path.strip("/").split("/"):
elem = elem.get(x)
except:
pass
return elem
Это не поддерживает такие вещи xpath, как индексы, конечно... не говоря уже о /
ключевая ловушка указана.
Одна из лучших библиотек, которую мне удалось идентифицировать, которая, кроме того, очень активно разрабатывается, это извлеченный проект из boto: JMESPath. Он обладает очень мощным синтаксисом выполнения действий, которые обычно требуют страниц кода для выражения.
Вот некоторые примеры:
search('foo | bar', {"foo": {"bar": "baz"}}) -> "baz"
search('foo[*].bar | [0]', {
"foo": [{"bar": ["first1", "second1"]},
{"bar": ["first2", "second2"]}]}) -> ["first1", "second1"]
search('foo | [0]', {"foo": [0, 1, 2]}) -> [0]
Есть более простой способ сделать это сейчас.
http://github.com/akesterson/dpath-python
$ easy_install dpath
>>> dpath.util.search(YOUR_DICTIONARY, "morefoo/morebar")
... сделанный. Или, если вам не нравится возвращать результаты в представление (объединенный словарь, в котором сохраняются пути), вместо этого выведите их:
$ easy_install dpath
>>> for (path, value) in dpath.util.search(YOUR_DICTIONARY, "morefoo/morebar", yielded=True)
... и готово. 'value' будет содержать {'bacon': 'foobar'} в этом случае.
Существует более новая библиотека jsonpath-rw, поддерживающая синтаксис JSONPATH, но для словарей и массивов python, как вы пожелаете.
Итак, ваш первый пример становится:
from jsonpath_rw import parse
print( parse('$.morefoo.morebar').find(foo) )
И второе
print( parse("$.morefoo[0].morebar.bacon").find(foo) )
PS: альтернативной более простой библиотекой, также поддерживающей словари, является python-json-pointer с более похожим на XPath синтаксисом.
dict > json > jmespath
Вы можете использовать JMESPath, который является языком запросов для JSON, и который имеет реализацию Python.
import jmespath # pip install jmespath
data = {'root': {'section': {'item1': 'value1', 'item2': 'value2'}}}
jmespath.search('root.section.item2', data)
Out[42]: 'value2'
Синтаксис запроса jmespath и примеры из жизни: http://jmespath.org/tutorial.html
dict> xml> xpath
Другой вариант - преобразовать ваши словари в XML, используя что-то вроде dicttoxml, а затем использовать регулярные выражения XPath, например, через lxml или любую другую библиотеку, которую вы предпочитаете.
from dicttoxml import dicttoxml # pip install dicttoxml
from lxml import etree # pip install lxml
data = {'root': {'section': {'item1': 'value1', 'item2': 'value2'}}}
xml_data = dicttoxml(data, attr_type=False)
Out[43]: b'<?xml version="1.0" encoding="UTF-8" ?><root><root><section><item1>value1</item1><item2>value2</item2></section></root></root>'
tree = etree.fromstring(xml_data)
tree.xpath('//item2/text()')
Out[44]: ['value2']
Если вам нравится краткость:
def xpath(root, path, sch='/'):
return reduce(lambda acc, nxt: acc[nxt],
[int(x) if x.isdigit() else x for x in path.split(sch)],
root)
Конечно, если у вас есть только слова, тогда все проще:
def xpath(root, path, sch='/'):
return reduce(lambda acc, nxt: acc[nxt],
path.split(sch),
root)
Удачи в поиске ошибок в вашем пути, хотя;-)
Нужно больше работать над тем, как будет работать XPath-подобный селектор.'/'
является правильным словарным ключом, так как бы
foo={'/':{'/':'eggs'},'//':'ham'}
быть обработанным?
foo.select("///")
было бы неоднозначно.
Другая альтернатива (помимо предложенной желе):
def querydict(d, q):
keys = q.split('/')
nd = d
for k in keys:
if k == '':
continue
if k in nd:
nd = nd[k]
else:
return None
return nd
foo = {
'spam':'eggs',
'morefoo': {
'bar':'soap',
'morebar': {'bacon' : 'foobar'}
}
}
print querydict(foo, "/morefoo/morebar")
Есть ли причина для вас, чтобы запросить его так же, как шаблон XPath? Как предположил комментатор вашего вопроса, это всего лишь словарь, поэтому вы можете получить доступ к элементам в виде гнезда. Кроме того, учитывая, что данные представлены в форме JSON, вы можете использовать модуль simplejson для его загрузки и доступа к элементам.
Есть проект JSONPATH, который пытается помочь людям делать то, что вы собираетесь делать (учитывая XPATH, как сделать его легко доступным через объекты Python), который кажется более полезным.
def Dict(var, *arg, **kwarg):
""" Return the value of an (imbricated) dictionnary, if all fields exist else return "" unless "default=new_value" specified as end argument
Avoid TypeError: argument of type 'NoneType' is not iterable
Ex: Dict(variable_dict, 'field1', 'field2', default = 0)
"""
for key in arg:
if isinstance(var, dict) and key and key in var: var = var[key]
else: return kwarg['default'] if kwarg and 'default' in kwarg else "" # Allow Dict(var, tvdbid).isdigit() for example
return kwarg['default'] if var in (None, '', 'N/A', 'null') and kwarg and 'default' in kwarg else "" if var in (None, '', 'N/A', 'null') else var
foo = {
'spam':'eggs',
'morefoo': {
'bar':'soap',
'morebar': {'bacon' : 'foobar'}
}
}
print Dict(foo, 'morefoo', 'morebar')
print Dict(foo, 'morefoo', 'morebar', default=None)
Иметь функцию SaveDict(value, var, *arg), которая может даже добавлять в списки в dict...
Я ссылаюсь на эту ссылку..
Следующий код предназначен для базового синтаксического анализа json xpath, реализованного на Python:
import json
import xmltodict
# Parse the json string
class jsonprase(object):
def __init__(self, json_value):
try:
self.json_value = json.loads(json_value)
except Exception :
raise ValueError('must be a json str value')
def find_json_node_by_xpath(self, xpath):
elem = self.json_value
nodes = xpath.strip("/").split("/")
for x in range(len(nodes)):
try:
elem = elem.get(nodes[x])
except AttributeError:
elem = [y.get(nodes[x]) for y in elem]
return elem
def datalength(self, xpath="/"):
return len(self.find_json_node_by_xpath(xpath))
@property
def json_to_xml(self):
try:
root = {"root": self.json_value}
xml = xmltodict.unparse(root, pretty=True)
except ArithmeticError :
pyapilog().error(e)
return xml
Тестовый Json:
{
"responseHeader": {
"zkConnected": true,
"status": 0,
"QTime": 2675,
"params": {
"q": "TxnInitTime:[2021-11-01T00:00:00Z TO 2021-11-30T23:59:59Z] AND Status:6",
"stats": "on",
"stats.facet": "CountryCode",
"rows": "0",
"wt": "json",
"stats.field": "ItemPrice"
}
},
"response": {
"numFound": 15162439,
"start": 0,
"maxScore": 1.8660598,
"docs": []
}
}
Тестовый код для чтения значений из входного файла json.
numFound = jsonprase(ABOVE_INPUT_JSON).find_json_node_by_xpath('/response/numFound')
print(numFound)