从Django模型生成层次JSON树结构

1 投票
2 回答
1934 浏览
提问于 2025-04-18 11:49

我有一个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

你可以这样做:

  1. 把一组 Classifications 转换成一个嵌套的字典。
  2. 把嵌套的字典转换成所需的格式。

这里的示例会使用稍微简化的 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')

我觉得这个过程不需要多解释——我们只是把传入的字典转换成所需的格式,并递归处理它的内容来形成子节点。

完整的示例可以在 这里 找到。

有两点需要注意:

  1. 这个代码是用 Python 3 写的。如果把 source.items() 替换成 source.iteritems(),就可以在 Python 2 中运行了。
  2. 你没有说明叶子节点应该是什么样的,所以我假设叶子节点应该是 genus,并且所有的 species 作为 children 附加在上面。如果你想让 species 成为叶子节点,修改代码来实现这一点也很简单。如果你在这方面遇到任何问题,请在评论中告诉我。

撰写回答