Общая проблема
Проблема с этой задачей (и другими подобными) заключается не только в том, как создать алгоритм - я уверен, что вы теоретически сможете решить эту проблему с (не очень) хорошимколичество вложенных циклов. Проблема состоит в том, чтобы организовать код так, чтобы у вас не было головной боли - то есть таким образом, чтобы вы могли легко исправлять ошибки, чтобы вы могли писать юнит-тесты, чтобы вы могли легко понять код, прочитав его (через шесть месяцевс этого момента) и что вы можете легко изменить свой код в случае необходимости. Я не знаю никого, кто не совершает ошибок, когда оборачивает голову глубоко вложенной структурой. И погоня за ошибками в коде, который сильно вложен, потому что он отражает вложенную структуру данных, может быть довольно неприятным.
Быстрое (и, скорее всего, лучшее) решение
Положитесь напакеты, созданные для вашего точного варианта использования, такие как
https://github.com/cwacek/python-jsonschema-objects
Если у вас есть формальное определение схемы API, вы можете использовать пакеты для этого. Если, например, ваш API имеет определение схемы Swagger, вы не можете использовать swagger-py
(https://github.com/digium/swagger-py)) для получения своего ответа JSON в объекты Python.
Принципиальное решение: объектно-ориентированное программирование и рекурсия
Даже если для вашего конкретного случая использования могут быть какие-то библиотеки, я хотел бы объяснить принцип работы с «такого рода» задачами:
Хороший способ организации кодадля такого рода проблем используется объектно-ориентированное программирование . Сложность вложения может быть изложена гораздо яснее, если использовать принцип рекурсия . Это также облегчает изменение кодав случае, если схема JSON вашего ответа API изменяется по каким-либо причинам (например, обновление API). В вашем случае я бы предложил вам создать что-то вроде следующего:
class JsonObject:
"""Parent Class for any Object that will be retrieved from the JSON
and potentially has nested JsonObjects inside.
This class takes care of parsing the json into python Objects and deals
with the recursion into the nested structures."""
primitives = []
json_objects = {
# For each class, this dict defines all the "embedded" classes which
# live directly "under" that class in the nested JSON. It will have the
# following structure:
# attribute_name : class
# In your case the JSON schema does not have any "single" objects
# in the nesting strcuture, but only lists of nested objects. I
# still , to demonstrate how you would do it in case, there would be
# single "embedded"
}
json_object_lists = {
# For each class, this dict defines all the "embedded" subclasses which
# are provided in a list "under" that class in the nested JSON.
# It will have the following structure:
# attribute_name : class
}
@classmethod
def from_dict(cls, d: dict) -> "JsonObject":
instance = cls()
for attribute in cls.primitives:
# Here we just parse all the primitives
instance.attribute = getattr(d, attribute, None)
for attribute, klass in cls.json_object_lists.items():
# Here we parse all lists of embedded JSON Objects
nested_objects = []
l = getattr(d, attribute, [])
for nested_dict in l:
nested_objects += klass.from_dict(nested_dict)
setattr(instance, attribute, nested_objects)
for attribute, klass in cls.json_objects.items():
# Here we parse all "single" embedded JSON Objects
setattr(
instance,
attribute,
klass.from_dict(getattr(d, attribute, None)
)
def to_csv(self) -> str:
pass
Так как вы не сделалиНе объясните, как именно вы хотите создать CSV из JSON, я не реализовал этот метод и оставил это вам. Также нет необходимостиОбосновать общий подход.
Теперь у нас есть общий родительский класс, от которого все наши специфические наследуются, чтобы мы могли применить рекурсию к нашей проблеме. Теперь нам нужно только определить эти конкретные структуры в соответствии со схемой JSON, которую мы хотим проанализировать. Я получил следующее из вашего примера, но вы можете легко изменить то, что вам нужно:
class Address(JsonObject):
primitives = [
"AddressID",
"AddressTypeID",
"Address",
"Street",
"City",
"State",
"PostalCode",
"IsPrimary",
"ParentID",
"ParentType",
]
json_objects = {}
json_object_lists = {}
class Lease(JsonObject):
primitives = [
"LeaseID",
"TenantID",
"UnitID",
"PropertyID",
"MoveInDate",
"SortOrder",
"CreateDate",
"UpdateDate",
"CreateUserID",
"UpdateUserID",
]
json_objects = {}
json_object_lists = {}
class UserDefinedValue(JsonObject):
primitives = [
"UserDefinedValueID",
"UserDefinedFieldID",
"ParentID",
"Name",
"Value",
"UpdateDate",
"FieldType",
"UpdateUserID",
"CreateUserID",
]
json_objects = {}
json_object_lists = {}
class PhoneNumber(JsonObject):
primitives = [
"PhoneNumberID",
"PhoneNumberTypeID",
"PhoneNumber",
"Extension",
"StrippedPhoneNumber",
"IsPrimary",
"ParentID",
"ParentType",
]
json_objects = {}
json_object_lists = {}
class Contact(JsonObject):
primitives = [
"ContactID",
"FirstName",
"LastName",
"MiddleName",
"IsPrimary",
"DateOfBirth",
"FederalTaxID",
"Comment",
"Email",
"License",
"Vehicle",
"IsShowOnBill",
"Employer",
"ApplicantType",
"CreateDate",
"CreateUserID",
"UpdateDate",
"AnnualIncome",
"UpdateUserID",
"ParentID",
"ParentType",
]
json_objects = {}
json_object_lists = {
"PhoneNumbers": PhoneNumber,
}
class Tenant(JsonObject):
primitives = [
"TenantID",
"TenantDisplayID",
"Name",
"FirstName",
"LastName",
"WebMessage",
"Comment",
"RentDueDay",
"RentPeriod",
"FirstContact",
"PropertyID",
"PostingStartDate",
"CreateDate",
"CreateUserID",
"UpdateDate",
"UpdateUserID",
"OpenReceivables", # Maybe this is also a nested Object? Not clear from your sample.
"Status",
]
json_object_lists = {
"Contacts": Contact,
"UserDefinedValues": UserDefinedValue,
"Leases": Lease,
"Addresses": Address,
}
json_objects = {}
Вы можете представить себе «красоту» (по крайней мере: порядок) этого подхода, которая заключается в следующемС помощью этой структуры мы могли бы справиться с любым уровнем вложенности в ответе JSON вашего API без дополнительной головной боли - наш код не углубил бы его уровень отступа, потому что мы разделили неприятное вложение на рекурсивное определение JsonObject
s from_json
метод. Вот почему теперь гораздо проще выявлять ошибки или применять изменения в нашем коде.
Чтобы наконец разобрать JSON сейчас в наших объектах, вы должны сделать что-то вроде следующего:
import typing
import json
def tenants_from_json(json_string: str) -> typing.Iterable["Tenant"]:
tenants = [
Tenant.from_dict(tenant_dict)
for tenant_dict in json.loads(json_string)
]
return tenants
Важное заключительное примечание: это просто основной принцип
Мой пример кода - это только очень краткое введение в идею использования объектов и рекурсии для борьбы с подавляющим (и неприятным) вложением структуры. Код имеет некоторые недостатки. Например, следует избегать определения переменных переменных класса. И, конечно, весь код должен проверять данные, которые он получает от API. Вы также можете добавить тип каждого атрибута и правильно представить его в объектах Python (например, в вашем образце есть целые числа, даты и время).
Я просто хотел показать вам сам принцип объектно-ориентированного программирования.
Я не потратил время на тестирование своего кода. Так что, вероятно, остались ошибки. Опять же, я просто хотел продемонстрировать принцип.