如何将此数据编码为 JSON 中的父/子结构
Posted
技术标签:
【中文标题】如何将此数据编码为 JSON 中的父/子结构【英文标题】:how to encode this data to parent / children structure in JSON 【发布时间】:2012-08-21 01:14:29 【问题描述】:我正在使用 d3.js 将动物(有机体)家族(一次最多 4000 个)可视化为树形图,尽管数据源也可以是目录列表或命名空间对象的列表。我的数据如下:
json =
organisms:[
name: 'Hemiptera.Miridae.Kanakamiris',
name: 'Hemiptera.Miridae.Neophloeobia.incisa',
name: 'Lepidoptera.Nymphalidae.Ephinephile.rawnsleyi',
... etc ...
]
我的问题是:我正在尝试找到将上述数据转换为分层父/子数据结构的最佳方法,如treemap 等许多 d3 可视化所使用的(有关数据示例,请参阅@987654322 @ 在 d3/examples/data/ 目录中)。 以下是所需数据结构的示例:
"name": "ROOT",
"children": [
"name": "Hemiptera",
"children": [
"name": "Miridae",
"children": [
"name": "Kanakamiris", "children":[],
"name": "Neophloeobia",
"children": [
"name": "incisa", "children":[]
]
]
],
"name": "Lepidoptera",
"children": [
"name": "Nymphalidae",
"children": [
"name": "Ephinephile",
"children": [
"name": "rawnsleyi", "children":[]
]
]
]
]
编辑:将所有原始所需的数据结构包含在ROOT
节点内,以符合只有一个主父节点的 d3 示例的结构。
我希望了解一般的设计模式,作为奖励,我希望看到一些 javascript、php(甚至 python)中的解决方案。 javascript是我的偏好。 关于 php:我实际使用的数据来自一个将结果编码为 json 的 php 脚本对数据库的调用。 如果这对基于 php 的答案有任何用途,则 php 脚本中的数据库结果是一个有序数组(见下文)。
Array
(
[0] => Array
(
['Rank_Order'] => 'Hemiptera'
['Rank_Family'] => 'Miridae'
['Rank_Genus'] => 'Kanakamiris'
['Rank_Species'] => ''
) ........
在哪里:
'Rank_Order'
isParentOf 'Rank_Family'
isParentOf 'Rank_Genus'
isParentOf 'Rank_Species'
我问了一个类似的问题,专注于 php 解决方案here,但唯一的答案在我的服务器上不起作用,我不太明白发生了什么,所以我想从设计模式的角度来问这个问题,并包括对我在 javascript 和 d3.js 中的实际使用的引用。
【问题讨论】:
您可以使用更压缩的语法,例如i.stack.imgur.com/V5zg0.jpg 根据您的描述,在从数据库中提取数据时,这听起来最好在服务器端完成。您想要的结构应该有方括号[]
而不是最外面的大括号
,因为您将它用作数组而不是对象。 (另请注意,您在这里不是在谈论 JSON,而是在谈论 JS 对象。您将数据序列化为 JSON 以将其发送给客户端,但是您想要做的操作是在 JSON 被解析为之后一个 JS 对象。)
@Oriol 和 @nnnnnn 我要匹配的所需数据结构/语法是 d3 示例中使用的结构,因此我不必修改该库。该库使用密钥name
和children
。 @nnnnnn 你是对的,我应该在最外面使用方括号,但这让我意识到我需要添加一个级别,我将调用root
而不是将现有结构作为它的子级,以便符合 d3 树形图库。我将编辑示例“所需结构”
@nnnnnn 我已经进行了编辑,因此该结构被包含为ROOT
节点的子节点,因此我仍在使用最外面的大括号。再次这是因为我想按原样使用 d3 库
@Oriol 您能否描述一种将初始“平面”数据转换为 javascript 或 php 中的“嵌套”数组结构的方法?不过,让事情变得更压缩是个好建议!
【参考方案1】:
以下内容特定于您提供的结构,可以相当容易地使其更通用。我确信 addChild 函数可以简化。希望 cmets 对您有所帮助。
function toHeirarchy(obj)
// Get the organisms array
var orgName, orgNames = obj.organisms;
// Make root object
var root = name:'ROOT', children:[];
// For each organism, get the name parts
for (var i=0, iLen=orgNames.length; i<iLen; i++)
orgName = orgNames[i].name.split('.');
// Start from root.children
children = root.children;
// For each part of name, get child if already have it
// or add new object and child if not
for (var j=0, jLen=orgName.length; j<jLen; j++)
children = addChild(children, orgName[j]);
return root;
// Helper function, iterates over children looking for
// name. If found, returns its child array, otherwise adds a new
// child object and child array and returns it.
function addChild(children, name)
// Look for name in children
for (var i=0, iLen=children.length; i<iLen; i++)
// If find name, return its child array
if (children[i].name == name)
return children[i].children;
// If didn't find name, add a new object and
// return its child array
children.push('name': name, 'children':[]);
return children[children.length - 1].children;
【讨论】:
将您的解决方案与@nnnnnn 的解决方案进行比较(仅包含 400 个生物体和 4 个分类级别),它似乎确实更快(大约 2 倍)。此解决方案与 nnnnnn 解决方案之间的区别在于处理零长度字符串的方式。此解决方案将为那些零长度名称创建子节点,包括沿分支和“叶”节点,而 nnnnnn 不会为零长度名称创建子节点,并且如果存在零长度名称,也会截断“分支”沿着“路径/分支”。显然,我从未为这种情况指定所需的行为! +1 工作答案 零长度字符串的行为是人工制品(我怀疑 nnnnnn 也是),我原以为它们是无效的。如果它们应该具有特定的行为,您应该指出它以及它们在原始数据中可能出现的方式。 是的,我同意,因此零长度字符串将在它们到达之前被无效或处理。对于任何对我的数据源感兴趣的人,我什至有像“Rank_super-family”这样的字段,这是可选的,以及示例数据中显示的一些没有“Rank_Species”的条目。所有这些零长度字符串都将“消失” 顺便说一句,RobG 解决方案似乎比 nnnnnn 解决方案更快(仅在初始测试中)【参考方案2】:鉴于您的初始输入,我相信类似以下代码的内容会产生您想要的输出。我不认为这是最漂亮的方法,但这是当时想到的。
预处理数据似乎最简单,首先将初始字符串数组拆分为一个数组数组,如下所示:
[
["Hemiptera","Miridae","Kanakamiris" ],
["Hemiptera","Miridae","Neophloeobia","incisa" ],
//etc
]
...然后对其进行处理以获取如下形式的工作对象:
working =
Hemiptera :
Miridae :
Kanakamiris : ,
Neophloeobia :
incisa :
,
Lepidoptera :
Nymphalidae :
Ephinephile :
rawnsleyi :
...因为使用对象而不是数组可以更容易地测试子项是否已经存在。创建了上述结构后,我最后一次处理它以获得最终所需的输出。所以:
// start by remapping the data to an array of arrays
var organisms = data.organisms.map(function(v)
return v.name.split(".");
);
// this function recursively processes the above array of arrays
// to create an object whose properties are also objects
function addToHeirarchy(val, level, heirarchy)
if (val[level])
if (!heirarchy.hasOwnProperty(val[level]))
heirarchy[val[level]] = ;
addToHeirarchy(val, level + 1, heirarchy[val[level]]);
var working = ;
for (var i = 0; i < organisms.length; i++)
addToHeirarchy(organisms[i], 0, working);
// this function recursively processes the object created above
// to create the desired final structure
function remapHeirarchy(item)
var children = [];
for (var k in item)
children.push(
"name" : k,
"children" : remapHeirarchy(item[k])
);
return children;
var heirarchy =
"name" : "ROOT",
"children" : remapHeirarchy(working)
;
演示:http://jsfiddle.net/a669F/1/
【讨论】:
我喜欢你使用hasOwnProperty
的方式。在初始测试中,这确实将原始数据转换为所需的格式。我认为这是我需要学习的递归函数模式,并且会从中学到最多。为这个答案 +1【参考方案3】:
我自己问题的另一种答案....在过去的一天里,我对 d3.js 以及与这个问题 d3.nest() 的 .key() 和 .entries() 相关的知识不多是我的朋友(所有 d3 功能)。 该答案涉及更改初始数据,因此它可能不符合我提出的具体问题的良好答案。但是,如果有人有类似的问题并且可以更改服务器上的内容,那么这是一个非常简单的解决方案:
以这种格式从数据库中返回数据:
json = 'Organisms': [
'Rank_Order': 'Hemiptera',
'Rank_Family': 'Miridae',
'Rank_Genus': 'Kanakamiris',
'Rank_Species': '' ,
, ...
]
然后使用d3.nest()
organismNest = d3.nest()
.key(function(d)return d.Rank_Order;)
.key(function(d)return d.Rank_Family;)
.key(function(d)return d.Rank_Genus;)
.key(function(d)return d.Rank_Species;)
.entries(json.Organism);
返回:
key: "Hemiptera"
values: [
key: "Cicadidae"
values: [
key: "Pauropsalta "
values: [
key: "siccanus"
values: [
Rank_Family: "Cicadidae"
Rank_Genus: "Pauropsalta "
Rank_Order: "Hemiptera"
Rank_Species: "siccanus"
AnotherOriginalDataKey: "original data value"
etc etc, nested and lovely
这返回的内容与我在上面问题中描述为我想要的格式的它们的数组非常相似,但有一些区别。特别是,没有所有封闭的 ROOT 元素,而且我最初想要的键是“name”和“children”。nest() 分别将键返回为“key”和“values”。 这些替代键很容易在 d3.js 中使用,只需定义适当的数据访问器函数(基本 d3 概念)......但这超出了问题的原始范围......希望对某人也有帮助
【讨论】:
以上是关于如何将此数据编码为 JSON 中的父/子结构的主要内容,如果未能解决你的问题,请参考以下文章