Я импортирую и обрабатываю некоторые глубоко вложенные JSON (импортированные в качестве словаря). Он может точно назначать значения с помощью кода, например:
query['query']['function_score']['query']['multi_match']['operator'] = 'or'
query['query']['function_score']['query']['multi_match'].update({
'minimum_should_match' : '80%' })
Но это уродливо и громоздко, как орехи. Мне интересно, есть ли более чистый способ назначения значений для глубоко вложенных ключей, которые достаточно эффективны?
Я читал о возможности использования SQL-памяти SQL-памяти в памяти, но данные возвращаются в json после небольшого количества манипуляций.
Ответ 1
multi_match = query['query']['function_score']['query']['multi_match']
multi_match['operator'] = 'or'
multi_match.update({'minimum_should_match' : '80%' })
Ответ 2
JSONPath (через 'jsonpath_rw') делает его менее громоздким:
Предыдущая:
>>> query
{u'query': {u'function_score': {u'query': {u'multi_match': {u'min_should_match': u'20%'}}}}}
Update:
>>> found = jsonpath_rw.parse("$..multi_match").find(query)[0]
>>> found.value["operator"] == "or"
>>> found.value["min_should_match"] = "80%"`
Далее:
>>> query
{u'query': {u'function_score': {u'query': {u'multi_match': {'min_should_match': '80%', u'operator': u'or'}}}}}
Ответ 3
Выбранный ответ определенно - путь. Проблема, которую я обнаружил позже, заключается в том, что мой вложенный ключ может отображаться на разных уровнях. Поэтому мне нужно было пройти через dict и сначала найти путь к node, а THEN выполнить обновление или добавление.
jsonpath_rw было немедленным решением, но я получил некоторые странные результаты, пытаясь его использовать. Я бросил после пары часов борьбы с ним.
Рискуя стать сбитым за то, что я был неуклюжим новичком, я в конечном итоге выделил несколько функций (на основе другого кода, который я нашел на SO), который изначально сделал некоторые приятные вещи для удовлетворения моих потребностей:
def find_in_obj(obj, condition, path=None):
''' generator finds full path to nested dict key when key is at an unknown level
borrowed from http://stackoverflow.com/a/31625583/5456148'''
if path is None:
path = []
# In case this is a list
if isinstance(obj, list):
for index, value in enumerate(obj):
new_path = list(path)
new_path.append(index)
for result in find_in_obj(value, condition, path=new_path):
yield result
# In case this is a dictionary
if isinstance(obj, dict):
for key, value in obj.items():
new_path = list(path)
new_path.append(key)
for result in find_in_obj(value, condition, path=new_path):
yield result
if condition == key:
new_path = list(path)
new_path.append(key)
yield new_path
def set_nested_value(nested_dict, path_list, key, value):
''' add or update a value in a nested dict using passed list as path
borrowed from http://stackoverflow.com/a/11918901/5456148'''
cur = nested_dict
path_list.append(key)
for path_item in path_list[:-1]:
try:
cur = cur[path_item]
except KeyError:
cur = cur[path_item] = {}
cur[path_list[-1]] = value
return nested_dict
def update_nested_dict(nested_dict, findkey, updatekey, updateval):
''' finds and updates values in nested dicts with find_in_dict(), set_nested_value()'''
return set_nested_value(
nested_dict,
list(find_in_obj(nested_dict, findkey))[0],
updatekey,
updateval
)
find_in_obj() - это генератор, который находит путь к данному вложенному ключу.
set_nested_values () будет либо обновлять ключ/значение в dict с заданным списком, либо добавлять его, если он не существует.
update_nested_dict() является "оболочкой" для двух функций, которые берут во вложенном dict для поиска, ключа, который вы ищете, и значения ключа для обновления (или добавляете, если оно существует).
Итак, я могу пройти:
q = update_nested_dict(q, 'multi_match', 'operator', 'or')
q = update_nested_dict(q, 'multi_match', 'minimum_should_match', '80%')
И значение "operator" обновляется, а ключ/значение "minimum_should_match" добавляется под "multi_match" node, независимо от того, какой уровень он отображается в словаре.
Может возникнуть проблема, если поиск найденного ключа существует в более чем 1 месте в словаре.