Как преобразовать строку в dict в иерархической структуре данных функциональным способом в python?

#python #python-3.x #functional-programming

Вопрос:

Давайте предположим, что у меня есть следующая структура данных python, где runtimeArgs содержится строка json, а не диктант python.

 [  {  "runid":"57d45a60-2b34-11ec-8b92-16c898d5a004",  "properties":{  "runtimeArgs":"{"date":"2021_10_11","logical.start.time":"1634026435347"}",  "phase-1":"7bc31901-2b34-11ec-9194-42010a0c0054"  }  },  {  "runid":"24f7887e-2b28-11ec-b60c-16c898d5a004",  "properties":{  "runtimeArgs":"{"date":"2021_10_11","logical.start.time":"1634021196053"}",  "phase-1":"4712bfa1-2b28-11ec-8968-42010a0c005a"  }  } ]  # this is working my_list[0]["properties"]['runtimeArgs']  # this not working my_list[0]["properties"]['runtimeArgs']['date']  

С for циклом, и json.loads() я мог бы преобразовать это в python dict, но я хочу сделать это более функциональным способом, например: понимание карты или списка и т. Д.

Как это возможно сделать?

Ожидаемый результат:

 [  {  "runid":"57d45a60-2b34-11ec-8b92-16c898d5a004",  "properties":{  "runtimeArgs":{"date":"2021_10_11","logical.start.time":"1634026435347"},  "phase-1":"7bc31901-2b34-11ec-9194-42010a0c0054"  }  },  {  "runid":"24f7887e-2b28-11ec-b60c-16c898d5a004",  "properties":{  "runtimeArgs":{"date":"2021_10_11","logical.start.time":"1634021196053"},  "phase-1":"4712bfa1-2b28-11ec-8968-42010a0c005a"  }  } ]  # this should work my_list[0]["properties"]['runtimeArgs']['date']  

Обновить:

один из способов, которым я мог бы разобраться в этом самостоятельно (что мне не нравится), заключается в следующем:

 [{**x, "properties": { "runtimeArgs" : json.loads(x["properties"]["runtimeArgs"]) }} for x in my_list if x]  

Есть ли более приятный способ сделать это?

Ответ №1:

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

потому что я сам хочу реализовать синтаксический анализатор JSON, функционально, для практики

В этом случае вы в основном сами по себе, но итеративный токенизатор-это, вероятно, правильный путь.

потому что что-то такое простое, как получение элемента x из этой строки в формате json, должно быть легким, и мне нужно сделать это только один раз

В этом случае используйте json.loads, но заключите свой доступ в функцию:

 def get_json(json, key):  return json.loads(json)[key]  get_json(l[0]["properties"], "date") # this is a function. I reckon that's functional. Here with a comprehension:  {l["runid"]: get_json(x["properties"], "date") for x in l}  

Потому что я хочу извлечь данные из структуры, десериализуя их по ходу

Используйте функцию:

 def get_parse(thing, key):  try:  return thing[key]  except ValueError:  return json.loads(thing)[key]  get_parse(get_parse(l[0], "properties"), "date")  

Эту функцию можно было бы сделать рекурсивной, если бы вы захотели, возвращая самый внутренний элемент.

Я не знаю, правильно ли эти причины описывают ваши варианты использования, но они могут помочь. Основной подход (дорогой для функционального программирования!) состоит в том, чтобы вложить сложную логику в функцию, а затем, если хотите, использовать ее в своем понимании.

Решение для JIT-анализа

Чисто для удовольствия, потому что это меня раздражало, вот класс JS-анализа, похожий на JIT:

 from json import loads   class JITParser:  def __init__(self, thing):  if not hasattr(thing, "__getitem__"):  self._thing = loads(thing)  else:  self._thing = thing   def get(self, key):  val = self._thing[key]  if isinstance(val, dict):  return JITParser(val)  else:  try:  return JITParser(loads(val))  except ValueError:  return val   def __repr__(self):  return f"JITParser with {repr(self._thing)}"   L = [  {  "runid": "57d45a60-2b34-11ec-8b92-16c898d5a004",  "properties": {  "runtimeArgs": '{"date":"2021_10_11","logical.start.time":"1634026435347"}',  "phase-1": "7bc31901-2b34-11ec-9194-42010a0c0054",  },  },  {  "runid": "24f7887e-2b28-11ec-b60c-16c898d5a004",  "properties": {  "runtimeArgs": '{"date":"2021_10_11","logical.start.time":"1634021196053"}',  "phase-1": "4712bfa1-2b28-11ec-8968-42010a0c005a",  },  }, ]  j = JITParser(L[0]) j.get("runid") j.get("properties") j.get("properties").get("runtimeArgs") j.get("properties").get("runtimeArgs").get("date")  

Этот класс будет анализировать дикты или представления диктов в формате json и возвращать объекты JITParser, обертывая их, пока не попадет в то, что не может быть проанализировано как JSON, и в этом случае он вернет сам объект.

Существует множество возможных улучшений: вы могли бы подумать о:

  • подкласс диктует и имеет [] доступ
  • реализовать рекурсивный анализ списков
  • обрабатывайте другие типы, например объекты с .dotaccess помощью .
  • и т.д.

но это было забавно смоделировать, и это может вдохновить вас, поэтому я оставлю это здесь. Попробуйте сами: это довольно весело.

Ответ №2:

Для этого конкретного набора данных вы могли бы сделать это:

 L = [  {  "runid":"57d45a60-2b34-11ec-8b92-16c898d5a004",  "properties":{  "runtimeArgs":"{"date":"2021_10_11","logical.start.time":"1634026435347"}",  "phase-1":"7bc31901-2b34-11ec-9194-42010a0c0054"  }  },  {  "runid":"24f7887e-2b28-11ec-b60c-16c898d5a004",  "properties":{  "runtimeArgs":"{"date":"2021_10_11","logical.start.time":"1634021196053"}",  "phase-1":"4712bfa1-2b28-11ec-8968-42010a0c005a"  }  } ]  for d in L:  print(eval(d['properties']['runtimeArgs'])['date'])  

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

1. json.loads, вероятно , безопаснее, чем eval, если только оператор действительно не знает, что данные всегда будут в безопасности…

2. @2e0byo Пожалуйста, обратите внимание на мой комментарий в начале моего ответа — т. Е. Это может не быть общим решением этой проблемы