Python: обновление значения в глубоко вложенном словаре

Я импортирую и обрабатываю некоторые глубоко вложенные 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 месте в словаре.