def is_node(value): return value in ("node", "element") def is_shape(value): return value in ("shape", "exact") def collect_array(a_value, base, nodes): a_type = a_value["name"] if is_node(a_type): nodes.append(base) elif a_type in ("shape", "exact"): nodes = collect_nodes(a_value["value"], base + "[]", nodes) elif a_type == "union": nodes = collect_union(a_value["value"], base + "[]", nodes) elif a_type == "objectOf": nodes = collect_object(a_value["value"], base + "[]", nodes) return nodes def collect_union(type_list, base, nodes): for t in type_list: if is_node(t["name"]): nodes.append(base) elif is_shape(t["name"]): nodes = collect_nodes(t["value"], base, nodes) elif t["name"] == "arrayOf": nodes = collect_array(t["value"], base, nodes) elif t["name"] == "objectOf": nodes = collect_object(t["value"], base, nodes) return nodes def collect_object(o_value, base, nodes): o_name = o_value.get("name") o_key = base + "{}" if is_node(o_name): nodes.append(o_key) elif is_shape(o_name): nodes = collect_nodes(o_value.get("value", {}), o_key, nodes) elif o_name == "union": nodes = collect_union(o_value.get("value"), o_key, nodes) elif o_name == "arrayOf": nodes = collect_array(o_value, o_key, nodes) return nodes def collect_nodes(metadata, base="", nodes=None): nodes = nodes or [] for prop_name, value in metadata.items(): # Support for recursive shapes, the type is directly in the field. t_value = value.get("type", value) p_type = t_value.get("name") if base: key = f"{base}.{prop_name}" else: key = prop_name if is_node(p_type): nodes.append(key) elif p_type == "arrayOf": a_value = t_value.get("value", t_value) nodes = collect_array(a_value, key, nodes) elif is_shape(p_type): nodes = collect_nodes(t_value["value"], key, nodes) elif p_type == "union": nodes = collect_union(t_value["value"], key, nodes) elif p_type == "objectOf": o_value = t_value.get("value", {}) nodes = collect_object(o_value, key, nodes) return nodes def filter_base_nodes(nodes): return [n for n in nodes if not any(e in n for e in ("[]", ".", "{}"))]