任意字典(JSON)转为严格树状字典(JSON)在Python中
大家好,
我有一些字典,这些字典里面可能包含任意数量的其他字典和数组,结构可能是这样的:
{
"a": {
"b": "1",
"c": [
{
"d": "2"
},
{
"d": {
"e": "3",
"f": "4"
}
}
]
}
}
我想把这些数据以树的形式展示出来,为了做到这一点,我想把它转换成这样的结构:
{
"name": "a",
"children": [
{
"name": "b",
"value": "1"
},
{
"name": "c",
"children": [
{
"name": "d",
"value": "2"
},
{
"name": "d",
"children": [
{
"name": "e",
"value": "3"
},
{
"name": "f",
"value": "4"
}
]
}
]
}
]
}
为了实现这个目标,我觉得需要把子字典和子数组处理得差不多。不过,我在想出一个方法来进行这种转换时遇到了很大的困难。我尝试过使用递归的方法,也试过创建一个节点类,这个类包含“名称”、“值”和“子节点”等信息,但我在解析一个深度不确定的输入,以创建这种常规的节点/树结构时遇到了麻烦。
这听起来像是大家之前做过的事情吗?希望能听到你们的意见。
1 个回答
4
你很聪明,正确地把这个结构看作树形数据结构,而且你决定转换它也是明智的,因为原来的表示方式显然有问题。
现在你知道这是一个树,你需要想想它的节点是什么。在你的例子中,节点可以是:
- 一个叶子节点,它有一个名字和一个值
- 一个内部节点,它有一个名字和一些子节点
问题是,第二种类型的节点有两种不同的表示方式:
- 一种是
("<name>", { "<child1>" : ..., "<child2>" })
- 另一种是
("<name>", [{"<child1>" : ... }, { "<child2>": ... }])
你的例子没有展示,但也许下面的形式也是可以的:
("<name>", [{"<child1>" : ... }, { "<child2>": ..., "<child3>": ... }])
使用递归确实是解决这个问题的好方法。最简单的情况就是叶子节点。你只需要先检查另外两种情况:
def transform_node(name, val):
if isinstance(val, list):
val = ("children", [transform_node(k,v) for x in val for k, v in x.items()])
elif isinstance(val, dict):
val = ("children", [transform_node(*kv) for kv in val.items()])
else:
val = ("value", val)
return dict([("name", name), val])
现在你得到了:
>>> transform_node("a", 3)
{'name': 'a', 'value': 3}
>>> transform_node("a", { "c1" : 3, "c2" : 4 })
{'name': 'a', 'children': [{'name': 'c2', 'value': 4}, {'name': 'c1', 'value': 3}]}
>>> transform_node("a", [{ "c1" : 3 }, { "c2" : 4, "c3": 5 }])
{'name': 'a', 'children': [{'name': 'c1', 'value': 3}, {'name': 'c3', 'value': 5}, {'name': 'c2', 'value': 4}]}
太好了!现在你可以直接这样做:
>>> tree = { "a": { "b": 1 }}
>>> transform_node("root", tree)
{'name': 'root', 'children': [{'name': 'a', 'children': [{'name': 'b', 'value': 1}]}]}
当然,如果你想让“a”成为根节点,你只需要提取根节点的第一个子节点。
不过,可能更好的是用类来表示这个结构,而不是用字典,因为字典是完全没有类型的。