сгладить вложенный словарь со словарем, встроенным в списки (функциональный python)

Эти вопросы задавались много раз, но только один раз в этом особом случае, и я смог частично найти ответ здесь, но он сводится к каждому объекту.

У меня есть этот словарь:

{'address': {'address_line_1': 'Floor Dekk House',
  'address_line_2': 'Zippora Street Providence Industrial Estate',
  'country': 'Seychelles',
  'locality': 'Mahe',
  'premises': '1st'},
 'address_snippet': '1st, Floor Dekk House, Zippora Street Providence Industrial Estate, Mahe, Seychelles',
 'appointment_count': 1,
 'description': 'Total number of appointments 1',
 'description_identifiers': ['appointment-count'],
 'kind': 'searchresults#officer',
 'links': {'self': '/officers/z7s5QUnhlYpAT8GvqvJ5snKmtHE/appointments'},
 'matches': {'snippet': [], 'title': [1, 8, 10, 11]},
 'snippet': '',
 'title': 'ASTROCOM AG '}

Как видите, пара "description_identifiers" и "matches.snippet" и "matches.title" have a list as value. I'd like to edit my code below to flatten my dictionary so that the json is flattened in a{ключ:значение, ключ:значение, ключ:значение}`, но если значение представляет собой список атомарных объектов (не список списков или список словарей), значение поддерживается в виде списка.

Цель состоит в том, чтобы иметь возможность загружать этот json в postgresql.

Вот код, который я нашел в Интернете:

def flatten_json(dictionary):
    """Flatten a nested json file"""

    def unpack(parent_key, parent_value):
        """Unpack one level of nesting in json file"""
        # Unpack one level only!!!

        if isinstance(parent_value, dict):
            for key, value in parent_value.items():
                temp1 = parent_key + '_' + key
                yield temp1, value
        elif isinstance(parent_value, list):
            i = 0 
            for value in parent_value:
                temp2 = parent_key + '_' +str(i) 
                i += 1
                yield temp2, value
        else:
            yield parent_key, parent_value    


    # Keep iterating until the termination condition is satisfied
    while True:
        # Keep unpacking the json file until all values are atomic elements (not dictionary or list)
        dictionary = dict(chain.from_iterable(starmap(unpack, dictionary.items())))
        # Terminate condition: not any value in the json file is dictionary or list
        if not any(isinstance(value, dict) for value in dictionary.values()) and \
           not any(isinstance(value, list) for value in dictionary.values()):
            break

    return dictionary

Желаемый результат:

И чтобы проверить, этот дикт: должно быть not (что я и получаю сейчас):

{'address_address_line_1': 'Floor Dekk House',
 'address_address_line_2': 'Zippora Street Providence Industrial Estate',
 'address_country': 'Seychelles',
 'address_locality': 'Mahe',
 'address_premises': '1st',
 'address_snippet': '1st, Floor Dekk House, Zippora Street Providence Industrial Estate, Mahe, Seychelles',
 'appointment_count': 1,
 'description': 'Total number of appointments 1',
 'description_identifiers_0': 'appointment-count',
 'kind': 'searchresults#officer',
 'links_self': '/officers/z7s5QUnhlYpAT8GvqvJ5snKmtHE/appointments',
 'matches_title_0': 1,
 'matches_title_1': 8,
 'matches_title_2': 10,
 'matches_title_3': 11,
 'snippet': '',
 'title': 'ASTROCOM AG '}

Но скорее

{'address_address_line_1': 'Floor Dekk House',
 'address_address_line_2': 'Zippora Street Providence Industrial Estate',
 'address_country': 'Seychelles',
 'address_locality': 'Mahe',
 'address_premises': '1st',
 'address_snippet': '1st, Floor Dekk House, Zippora Street Providence Industrial Estate, Mahe, Seychelles',
 'appointment_count': 1,
 'description': 'Total number of appointments 1',
 'description_identifiers_0': 'appointment-count',
 'kind': 'searchresults#officer',
 'links_self': '/officers/z7s5QUnhlYpAT8GvqvJ5snKmtHE/appointments',
 'matches_title': [1, 8, 10, 11]
 'snippet': '',
 'title': 'ASTROCOM AG '}

person Tytire Recubans    schedule 03.04.2019    source источник
comment
Не уверен, откуда вы взяли свой пример (публикация его очень помогает, кстати, многие плакаты не дают входных данных), но предполагается ли, что он действует как древовидная структура данных? .   -  person Elmex80s    schedule 04.04.2019
comment
Да, это так. Опубликованный мной JSON — это, по сути, самая полная версия ответа API. Однако некоторые поля являются необязательными, поэтому я пытаюсь получить функцию для выравнивания любого ввода, а затем использовать этот сглаженный словарь для вставки данных в postgresql. Проблема в том, что некоторые списки содержат целые числа/строки (в этом случае я хочу, чтобы эти списки оставались списками и становились массивами в postgresql), тогда как, если список содержит другой dict, его тоже рекурсивно распаковать. Любое решение до сих пор?   -  person Tytire Recubans    schedule 04.04.2019
comment
Я нашел этот, но он не обрабатывает случай хеджирования, списки распаковываются независимо от типа данных, которые они содержат.   -  person Tytire Recubans    schedule 04.04.2019
comment
По какой-то конкретной причине вы хотите использовать yield?   -  person Elmex80s    schedule 04.04.2019
comment
Не совсем, я уверен, что это можно сделать и с пониманием -   -  person Tytire Recubans    schedule 04.04.2019
comment
Как должен выглядеть результат для вашего примера?   -  person Elmex80s    schedule 04.04.2019
comment
Давайте продолжим обсуждение в чате.   -  person Tytire Recubans    schedule 04.04.2019


Ответы (1)


Вы почти закончили, за исключением того, что вам нужно немного больше проверить условие:

def flatten(dict_, prefix):
    for k, v in dict_.items():
        if isinstance(v, list) and len(v)==1:
            if isinstance(v[0], dict):
                for key, value in flatten(v[0], prefix+k+"_"):
                    yield key, value
            else:
                yield prefix+k+"_0", v[0]
        elif isinstance(v, dict):
            for key, value in flatten(v, prefix+k+"_"):
                yield key, value
        else:
            yield prefix+k, v

Применение:

dict_ = {'address': {'address_line_1': 'Floor Dekk House',
  'address_line_2': 'Zippora Street Providence Industrial Estate',
  'country': 'Seychelles',
  'locality': 'Mahe',
  'premises': '1st'},
 'address_snippet': '1st, Floor Dekk House, Zippora Street Providence Industrial Estate, Mahe, Seychelles',
 'appointment_count': 1,
 'description': 'Total number of appointments 1',
 'description_identifiers': ['appointment-count'],
 'kind': 'searchresults#officer',
 'links': {'self': '/officers/z7s5QUnhlYpAT8GvqvJ5snKmtHE/appointments'},
 'matches': {'snippet': [], 'title': [1, 8, 10, 11]},
 'snippet': '',
 'title': 'ASTROCOM AG '}

import json
print(json.dumps(dict(list(flatten(dict_, ""))), indent=4))

Выход:

{
    "address_address_line_1": "Floor Dekk House",
    "address_address_line_2": "Zippora Street Providence Industrial Estate",
    "address_country": "Seychelles",
    "address_locality": "Mahe",
    "address_premises": "1st",
    "address_snippet": "1st, Floor Dekk House, Zippora Street Providence Industrial Estate, Mahe, Seychelles",
    "appointment_count": 1,
    "description": "Total number of appointments 1",
    "description_identifiers_0": "appointment-count",
    "kind": "searchresults#officer",
    "links_self": "/officers/z7s5QUnhlYpAT8GvqvJ5snKmtHE/appointments",
    "matches_snippet": [],
    "matches_title": [
        1,
        8,
        10,
        11
    ],
    "snippet": "",
    "title": "ASTROCOM AG "
}
person adrtam    schedule 04.04.2019
comment
Это прекрасно! Я понял, что оставил странный 0 после "description_identifiers", поэтому я удалил +"_0" из второго выхода. - person Tytire Recubans; 04.04.2019
comment
каким-либо образом мы можем получить вывод без ведущих _ ? - person Tytire Recubans; 04.04.2019