Получение ключей через значения из вложенного файла json с помощью Python

#python #json #nested

Вопрос:

Я хотел бы извлечь путь ключей, значение которого-Уолли, из программы

 arr = []
sub_arr = []
def extract(obj, sub_arr, val):
    if isinstance(obj, dict):
        for k, v in obj.items():
            if isinstance(v, (dict, list)):    
                sub_arr.append(k)            
                extract(v, sub_arr, val)
            elif v == val:
                sub_arr.append(k)
                arr.append(sub_arr)
                sub_arr = []
    elif isinstance(obj, list):
        for item in obj:
            if isinstance(item, (dict, list)):
                sub_arr.append(obj.index(item))
                extract(item, sub_arr, val)      
            elif item == val:
                sub_arr.append(obj.index(item))
                arr.append(sub_arr)    
                sub_arr = []
    return arr

obj =  {
        "News": [
            {
                "Title": "NewsA",
                "Tags": ["Gossiping"],
                "Date": "2021/06/26",
                "Detail": {
                    "Author": "Wally",
                    "Content": "Hello World"
                }
            },
            {
                "Title": "NewsB",
                "Tags": ["Social", "Wally"],
                "Date": "2021/06/27",
                "Detail": {
                    "Author": "Andy",
                    "Content": "Taiwan NO.1"
                }
            }
        ]
    }
print(extract(obj, sub_arr, "Wally"))
 

Это лучший результат, который у меня пока есть

 [
  ['News', 0, 'Tags', 'Detail', 'Author', 1, 'Tags', 1, 'Detail']
, ['News', 0, 'Tags', 'Detail', 'Author', 1, 'Tags', 1, 'Detail']
]
 

Моя желаемая ценность была бы такой

 [['News', 0, 'Detail', 'Author'], ['News', 1, 'Tags', 1]]
 

Довольно сильно застрял прямо здесь. Есть ли что-то, что я пропустил? Был бы признателен за небольшую помощь

Комментарии:

1. Ценность-это Уолли — Какая ценность ? Там есть Автор и теги с Уолли в качестве значения

2. Но мне нужен корень ключа, например [«Новости», 0, «Подробности», «Автор»] и [«Новости», 1, «Теги», 1]

Ответ №1:

Списки изменчивы и передаются для каждой итерации вашей функции извлечения. В вашем случае sub_arr добавляется бесконечно, что объясняет полученный вами ответ. Поэтому всегда будьте осторожны при использовании списков таким образом.

Решением является создание нового списка для каждого вызова функции извлечения, например:

 arr = []
sub_arr = []
def extract(obj, sub_arr, val):
    if isinstance(obj, dict):
        for k, v in obj.items():
            found_arr = [*sub_arr, k]
            if isinstance(v, (dict, list)):
                extract(v, found_arr, val)
            elif v == val:
                arr.append(found_arr)
    elif isinstance(obj, list):
        for item in obj:
            found_arr = [*sub_arr, obj.index(item)]
            if isinstance(item, (dict, list)):
                extract(item, found_arr, val)
            elif item == val:
                arr.append(found_arr)
    return arr

obj = {
        "News": [
            {
                "Title": "NewsA",
                "Tags": ["Gossiping"],
                "Date": "2021/06/26",
                "Detail": {
                    "Author": "Wally",
                    "Content": "Hello World"
                }
            },
            {
                "Title": "NewsB",
                "Tags": ["Social", "Wally"],
                "Date": "2021/06/27",
                "Detail": {
                    "Author": "Andy",
                    "Content": "Taiwan NO.1"
                }
            }
        ]
    }
print(extract(obj, sub_arr, "Wally"))
 

Что дает желаемый ответ:

 [['News', 0, 'Detail', 'Author'], ['News', 1, 'Tags', 1]]
 

Комментарии:

1. Приятно, мне это легче понять, спасибо за большую помощь!

Ответ №2:

Самый большой недостаток-это повторное использование sub_arr . Любой рекурсивный механизм для структурирования этой программы должен каким-то образом интернировать или копировать частичные ключевые пути по мере ее спуска. Используя один и тот же объект, вы получите примерно одинаковый результат для каждой части пути (как вы видите в своем текущем решении, где оба ключевых пути идентичны).

В подобных программах мне также нравится создавать разные значения для своих входных данных как можно раньше, а не разветвлять поведение программы на основе типа входных данных. Я нахожу, что это легче понять и изменить. В частности, обратите внимание , как расширяется функция верхнего уровня вашего решения isinstance(obj, ...) , в то время как в моем решении мы можем превратить как списки, так и диктанты в своего рода повторяющиеся пары вещей for k, v in items(obj) .

 def items(obj):
  if isinstance(obj, dict):
    return obj.items()
  if isinstance(obj, list):
    return enumerate(obj)

def _extract(obj, target):
  # base case
  if not items(obj):
    if obj == target:
      yield []

  # recurse
  else:
    for k, v in items(obj):
      for L in _extract(v, target):
        # could use `yield [k, L]` here and
        # list(map(unpack, _extract(obj, target))) below
        # to avoid quadratic copying for long key paths
        yield [k, *L]

def extract(obj, target):
  return list(_extract(obj, target))

# if you opt for `yield [k, L]` solution
def unpack(L):
  result = []
  while L:
    k, L = L
    result.append(k)
  return result
 

Как бы в стороне, это может быть полезной идеей при изучении неизвестных ответов API и тому подобного. Реструктурируйте его, чтобы использовать предикат соответствующей функции, а не просто какое-то значение, которое вы ищете, и оно станет намного более универсальным.

 def _extract(obj, matches):
  if not items(obj):
    if matches(obj):
      yield []
  ...

# existing behavior
extract(obj, lambda x: x=='Wally')

# just a utility function
def false_on_error(f):
  def _f(*a, **k):
    try:
      return f(*a, **k)
    except:
      return False
  return _f

# find the keys giving values that kind of look like an email
import re
prog = re.compile(r'@.*?.')
extract(obj, false_on_error(lambda x: prog.search(x)))

# you saw a value on the site and want to know if anything
# kind of looks like it
#
# bad practice to depend on `or` short-circuiting like this
# to prevent errors...
extract(obj, false_on_error(lambda x: x == 98.6 or '98.6' in x))
 

Комментарии:

1. Отлично, это очень помогает! Премного благодарен

Ответ №3:

В вашем коде

 if isinstance(v, (dict, list)):    
     arr.append(k) 
 

вы добавляли k каждый раз, когда получали что-то новое dict или list результат был неправильным.
Кроме того, прохождение sub_arr через множество слоев усложнило управление вашим кодом.

У меня есть некоторые изменения логики в соответствии с вашей базой кода.

 def extract(obj, val):
    result = []
    if isinstance(obj, dict):
        for k, v in obj.items():
            if isinstance(v, str):
                if v == val:
                    result.append([k])
                    continue
            exts = extract(v, val)
            for e in exts:
                result.append([k] e)
    if isinstance(obj, list):
        count = 0
        for item in obj:
            if isinstance(item, str):
                if item == val:
                    result.append([count])
                    continue
            exts = extract(item, val)
            for e in exts:
                temp  = [count]
                temp.extend(e)
                result.append(temp)
            count =1
    return result
 

Комментарии:

1. Фантастика, большое спасибо за большую помощь!