def unpack_data(json_obj: Any, parent_class: Type = None) -> Any:
"""Recursive function to parse, clean, and assign custom data types to retrieved Yahoo Fantasy Sports data.
Args:
json_obj (Any): JSON object for parsing (can be a dictionary, list, or primitive).
parent_class (Type): Parent class type used to extract custom subclass type options for casting.
Returns:
Any: Recursively returns JSON objects until data is completely parsed, cleaned, and typed (where applicable).
"""
# extract subclasses from parent class for typing
subclasses = {}
if parent_class:
subclasses = {stringcase.snakecase(cls.__name__): cls for cls in parent_class.__subclasses__()}
# discard empty lists and dictionaries and include when json value = 0
if json_obj == 0 or json_obj:
# handle lists
if isinstance(json_obj, list):
json_obj = [obj for obj in json_obj if (obj == 0 or obj)]
if len(json_obj) == 1:
return unpack_data(json_obj[0], parent_class)
else:
# flatten list of dicts if any objects in the list are dicts
if any(isinstance(obj, dict) for obj in json_obj):
return flatten_json_dict_list(json_obj, parent_class)
return [unpack_data(obj, parent_class) for obj in json_obj if (obj == 0 or obj)]
# handle dictionaries
elif isinstance(json_obj, dict):
# eliminate odd single-key Yahoo dicts with key = "0" and value = <next layer of desired data>
if "0" in json_obj.keys() and "1" not in json_obj.keys():
if len(json_obj.keys()) == 1:
return unpack_data(json_obj.get("0"), parent_class)
else:
if isinstance(json_obj.get("0"), dict):
json_obj.update(json_obj.pop("0"))
# eliminate data obj counts (except in player_position dicts, which have position counts in league settings)
if "count" in json_obj.keys() and "position" in json_obj.keys():
# assign/cast data type where applicable
# TODO: figure out how to do this without explicit object type keys
return get_type(
{k: unpack_data(v, parent_class) for k, v in json_obj.items()},
parent_class,
subclasses
)
else:
# assign/cast data type where applicable
# TODO: figure out how to do this without explicit object type keys
json_obj = get_type(
{k: unpack_data(v, parent_class) for k, v in json_obj.items() if k != "count"},
parent_class,
subclasses
)
# flatten dicts with keys "0", "1",..., "n" to a list of objects
if "0" in json_obj.keys() and "1" in json_obj.keys():
json_obj = flatten_to_list(json_obj)
# TODO: figure out how to do this without breaking the above unpacking using explicit type keys
# else:
# # flatten dicts with redundant keys to a list of objects
# if len(json_obj.keys()) == 1 and len(json_obj.values()) == 1:
# key = list(json_obj.keys())[0]
# value = list(json_obj.values())[0]
# json_obj = value
return json_obj
else:
return convert_strings_to_numeric_equivalents(json_obj)