Питонический способ преобразования / сглаживания JSON, содержащего вложенные структуры table-as-list-of-dicts
Предположим, у меня есть таблица, представленная в JSON в виде списка dicts, где ключи каждого элемента одинаковы:
J = [
{
"symbol": "ETHBTC",
"name": "Ethereum",
:
},
{
"symbol": "LTC",
"name": "LiteCoin"
:
},
И предположим, мне нужен эффективный поиск, например
symbols['ETHBTC']['name']
Я могу трансформироваться с
symbols = { item['name']: item for item in J }
, производящие:
{
"ETHBTC": {
"symbol": "ETHBTC",
"name": "Ethereum",
:
},
"LTCBTC": {
"symbol": "LTCBTC",
"name": "LiteCoin",
:
},
(В идеале я бы также удалил теперь лишний
symbol
поле).
Однако что, если каждый элемент сам по себе содержит «таблицу как список слов»?
Вот более полный минимальный пример (я удалил строки, не относящиеся к проблеме):
J = {
"symbols": [
{
"symbol":"ETHBTC",
"filters":[
{
"filterType":"PRICE_FILTER",
"minPrice":"0.00000100",
},
{
"filterType":"PERCENT_PRICE",
"multiplierUp":"5",
},
],
},
{
"symbol":"LTCBTC",
"filters":[
{
"filterType":"PRICE_FILTER",
"minPrice":"0.00000100",
},
{
"filterType":"PERCENT_PRICE",
"multiplierUp":"5",
},
],
}
]
}
Итак, задача состоит в том, чтобы преобразовать эту структуру в:
J = {
"symbols": {
"ETHBTC": {
"filters": {
"PRICE_FILTER": {
"minPrice": "0.00000100",
:
}
Я могу написать
flatten
функция:
def flatten(L:list, key) -> dict:
def remove_key_from(D):
del D[key]
return D
return { D[key]: remove_key_from(D) for D in L }
Затем я могу сгладить внешний список и перебрать каждый ключ / значение в полученном слове, сглаживая
val['filters']
:
J['symbols'] = flatten(J['symbols'], key="symbol")
for symbol, D in J['symbols'].items():
D['filters'] = flatten(D['filters'], key="filterType")
Можно ли улучшить это, используя
glom
(или иным образом)?
Начальное преобразование не имеет ограничений производительности, но мне нужен эффективный поиск.
2 ответа
Поскольку у вас разные правила преобразования для разных ключей, вы можете вести список имен ключей, требующих «группировки»:
t = ['symbol', 'filterType']
def transform(d):
if (m:={a:b for a, b in d.items() if a in t}):
return {[*m.values()][0]:transform({a:b for a, b in d.items() if a not in m})}
return {a:b if not isinstance(b, list) else {x:y for j in b for x, y in transform(j).items()} for a, b in d.items()}
import json
print(json.dumps(transform(J), indent=4))
{
"symbols": {
"ETHBTC": {
"filters": {
"PRICE_FILTER": {
"minPrice": "0.00000100"
},
"PERCENT_PRICE": {
"multiplierUp": "5"
}
}
},
"LTCBTC": {
"filters": {
"PRICE_FILTER": {
"minPrice": "0.00000100"
},
"PERCENT_PRICE": {
"multiplierUp": "5"
}
}
}
}
}
Я не знаю, назовете ли вы это pythonic, но вы можете сделать свою функцию более универсальной, используя рекурсию и отбрасывая ключ в качестве аргумента. Поскольку вы уже предполагаете, что ваши списки содержат словари, вы можете извлечь выгоду из динамической типизации Python, сделав любой ввод:
from pprint import pprint
def flatten_rec(I) -> dict:
if isinstance(I, dict):
I = {k: flatten_rec(v) for k,v in I.items()}
elif isinstance(I, list):
I = { list(D.values())[0]: {k:flatten_rec(v) for k,v in list(D.items())[1:]} for D in I }
return I
pprint(flatten_rec(J))
Выход:
{'symbols': {'ETHBTC': {'filters': {'PERCENT_PRICE': {'multiplierUp': '5'},
'PRICE_FILTER': {'minPrice': '0.00000100'}}},
'LTCBTC': {'filters': {'PERCENT_PRICE': {'multiplierUp': '5'},
'PRICE_FILTER': {'minPrice': '0.00000100'}}}}}