从Django模型生成层次JSON树结构
我有一个Django模型,如下所示:
class Classification(models.Model):
kingdom = models.CharField(db_column='Kingdom', max_length=50)
phylum = models.CharField(db_column='Phylum', max_length=50)
class_field = models.CharField(db_column='Class', max_length=50)
order = models.CharField(db_column='Order', max_length=50)
family = models.CharField(db_column='Family', max_length=50)
genus = models.CharField(db_column='Genus', max_length=50)
species = models.CharField(db_column='Species', max_length=50)
这个模型是用来表示生物分类的,像下面这个图:
我有超过5000种物种的分类记录。现在我需要生成一个像下面这样的JSON层级结构。
{
'name': "root",
'children': [
{
'name': "Animalia",
'children': [
{
{
'name':"Chordata"
'children': [ ... ]
}
},
...
...
]
},
...
...
]
}
你能给我推荐一些方法来实现这个吗?
2 个回答
1
终于得到了我想要的东西。代码虽然不漂亮,甚至有点丑,但不知怎么的,我还是实现了我的目标。
def classification_flare_json(request):
#Extracting from database and sorting the taxonomy from left to right
clazz = Classification.objects.all().order_by('kingdom','phylum','class_field','genus','species')
tree = {'name': "root", 'children': []}
#To receive previous value of given taxa type
def get_previous(type):
types = ['kingdom', 'phylum', 'class_field', 'family', 'genus', 'species']
n = types.index(type)
sub_tree = tree['children']
if not sub_tree: return None
for i in range(n):
if not sub_tree: return None
sub_tree = sub_tree[len(sub_tree)-1]['children']
if not sub_tree: return None
last_item = sub_tree[len(sub_tree)-1]
return last_item['name']
#To add new nodes in the tree
def append(type, item):
types = ['kingdom', 'phylum', 'class_field', 'family', 'genus', 'species_id']
n = types.index(type)
sub_tree = tree['children']
for i in range(n+1):
if not sub_tree: return None
sub_tree = sub_tree[len(sub_tree)-1]['children']
sub_tree.append(item)
for item in clazz:
while True:
if item.kingdom == get_previous('kingdom'):
if item.phylum == get_previous('phylum'):
if item.class_field == get_previous('class_field'):
if item.family == get_previous('family'):
if item.genus == get_previous('genus'):
append('genus', {'name':item.species, 'size': 1})
break;
else:
append('family', {'name':item.genus, 'children': []})
else:
append('class_field', {'name':item.family, 'children':[]})
else:
append('phylum', {'name': item.class_field, 'children':[]})
else:
append('kingdom', {'name': item.phylum, 'children':[]})
else:
tree['children'].append({'name': item.kingdom, 'children':[]})
return HttpResponse(json.dumps(tree), content_type="application/json")
2
你可以这样做:
- 把一组
Classifications
转换成一个嵌套的字典。 - 把嵌套的字典转换成所需的格式。
这里的示例会使用稍微简化的 Classification
类,以便更容易理解:
class Classification:
def __init__(self, kingdom, phylum, klass, species):
self.kingdom = kingdom
self.phylum = phylum
self.klass = klass
self.species = species
第一部分:
from collections import defaultdict
# in order to work with your actual implementation add more levels of nesting
# as lambda: defaultdict(lambda: defaultdict(lambda: defaultdict(list)))
nested_dict = defaultdict(
lambda: defaultdict(
lambda: defaultdict(list)
)
)
for c in all_classifications:
nested_dict[c.kingdom][c.phylum][c.klass].append(c.species)
defaultdict
是一个很方便的工具,可以确保字典中某个键的存在。它接受任何可调用的对象,并用它来为缺失的键创建一个值。
现在我们得到了一个漂亮的嵌套字典,格式是:
{
'Kingdom1': {
'Phylum1': {
'Class1': ["Species1", "Species2"],
'Class2': ["Species3", "Species4"],
},
'Phylum2': { ... }
},
'Kingdom2': { 'Phylum3': { ... }, 'Phylum4': {... } }
}
第二部分:转换成所需的输出
def nested_to_tree(key, source):
result = {'name': key, 'children':[]}
for key, value in source.items():
if isinstance(value, list):
result['children'] = value
else:
child = nested_to_tree(key, value)
result['children'].append(child)
return result
tree = nested_to_tree('root', nested_dict')
我觉得这个过程不需要多解释——我们只是把传入的字典转换成所需的格式,并递归处理它的内容来形成子节点。
完整的示例可以在 这里 找到。
有两点需要注意:
- 这个代码是用 Python 3 写的。如果把
source.items()
替换成source.iteritems()
,就可以在 Python 2 中运行了。 - 你没有说明叶子节点应该是什么样的,所以我假设叶子节点应该是
genus
,并且所有的species
作为children
附加在上面。如果你想让species
成为叶子节点,修改代码来实现这一点也很简单。如果你在这方面遇到任何问题,请在评论中告诉我。