如何将树状的数组和对象的嵌套数据结构转换为具有计算/计数 id 和跟踪父 id 的项目列表?

Posted

技术标签:

【中文标题】如何将树状的数组和对象的嵌套数据结构转换为具有计算/计数 id 和跟踪父 id 的项目列表?【英文标题】:How does one convert a tree like nested data structure of arrays and objects into a list of items with computed/counted id's and tracked parent id's? 【发布时间】:2021-12-19 00:06:28 【问题描述】:

API 返回数组和对象的嵌套数据结构。数据以树状对象列表的形式出现,每个对象都有可能的父子关系。下面的示例代码显示了结构本身。

[
  label: "search me",
  value: "searchme",
  children: [

    label: "search me too",
    value: "searchmetoo",
    children: [

      label: "No one can get me",
      value: "anonymous",
    ],
  ],
, 
  label: "search me2",
  value: "searchme2",
  children: [

    label: "search me too2",
    value: "searchmetoo2",
    children: [

      label: "No one can get me2",
      value: "anonymous2",
    ],
  ],
]

上述数据必须转换为一个(平面)对象数组,其中每个对象将代表一个以前的节点元素,但具有唯一的主键 (id)。此外,节点的 parent-id 等于其父节点的 id(主键),但没有父节点的根节点除外,因此 parent-id 应该为 null。

上面提供的源数据的目标结构然后匹配下面的代码...

[
  id: 1,                // DIAGID
  parentId: null,       // PARENTID
  label: "search me",   // DIAGNOSIS
  value: "searchme"     // DIAGTYPE
, 
  id: 2,
  parentId: 1,
  label: "search me too",
  value: "searchmetoo"
, 
  id: 3,
  parentId: 2,
  label: "No one can get me",
  value: "anonymous"
, 
  id: 4,
  parentId: null,
  label: "search me2",
  value: "searchme2"
, 
  id: 5,
  parentId: 4,
  label: "search me too2",
  value: "searchmetoo2"
, 
  id: 6,
  parentId: 5,
  label: "No one can get me2",
  value: "anonymous2"
]

【问题讨论】:

尝试对json数据使用JSON.parse,可能会得到想要的结果。 那不是 JSON。 您所说的转换似乎做得更多:没有一个属性与输入中的任何内容匹配。请提供一对一致的输入/输出 JSON 没有单引号字符串文字。您将不得不再次更新。但更重要的是,如果你真的得到 JSON,你就必须解析它。我的假设是你得到一个对象,而不是 JSON。最好改写一下。你的标题还说你想要 JSON 作为输出?你确定吗?我猜你想要一个 javascript 数组,而不是 JSON(这是一种文本格式)。 请添加源数据和目标数据的一致数据。您尝试了哪些方法,哪些方法不起作用? 【参考方案1】:

您可以对Array#flatMap 采用递归方法并存储parent 以供下次调用。

这种方法为所有节点增加id

const
    flatTree = (id => parent => ( children = [], ...object ) => [
         id: ++id, ...object, parent ,
        ...children.flatMap(flatTree(id))
    ])(0),
    tree = [ label: 'search me', value: 'searchme', children: [ label: 'search me too', value: 'searchmetoo', children: [ label: 'No one can get me', value: 'anonymous' ] ] ,  label: 'four', searchme: '4four' ],
    flat = tree.flatMap(flatTree(null));

console.log(flat);
.as-console-wrapper  max-height: 100% !important; top: 0; 

【讨论】:

@PeterSeliger,对不起。请参阅编辑。 很好,flatMap(flatTree(/* ... */)) 最近成为了你的品牌推广方式,就像我似乎减少了一切一样。【参考方案2】:

我想说递归树遍历是您所需要的,但您可以使用生成器轻松完成同样的事情:

function *visitNodes( root, parent = null, id = 0 ) 
  
  const node = 
    ...root,
    id : ++id,
    parentId = parent ? parent.id : null
  ;
  delete node.children;
  
  yield node;
  
  for ( const child of root.children ?? [] ) 
    yield *visitNodes(child, node, id);
  
  

定义生成器后,您可以遍历节点:

for (const node of visitNodes( tree ) ) 
  // do something useful with node here

您可以使用扩展运算符轻松地将其转换为列表:

const nodes  = [...visitNodes(tree)];

或使用Array.from():

const nodes = Array.from( visitNodes(tree) );

【讨论】:

首先我认为你的意思是 parentId: parent ? parent.id : null 如果没有孩子你会得到 TypeError: root.children is not iterable @layth — 我假设叶节点有一个空的 children 数组。但是...代码已更改以处理缺少children 的情况。【参考方案3】:

这个版本是 trincot 和 Nicholas Carey 的想法的混搭。从 trincot 的回答(实际上回答了一个不同但密切相关的问题),我窃取了他的 ctr: id: 1 处理。从 Nicholas 那里,我使用生成器更轻松地遍历值,尽管我从那里简化了一点。

我认为它们结合起来提供了一个很好的解决方案:

const flatten = function * (xs, ctr = id: 1, parent = null) 
  for (let children = [], ...x of xs) 
    const node = id: ctr .id ++, parentId: parent ? parent .id : null, ...x
    yield node
    yield * flatten (children, ctr, node)
  


const transform = (xs) => [...flatten (xs)]

const response = [label: "search me", value: "searchme", children: [label: "search me too", value: "searchmetoo", children: [label: "No one can get me", value: "anonymous"]], label: "search me2", value: "searchme2", children: [label: "search me too2", value: "searchmetoo2", children: [label: "No one can get me2", value: "anonymous2"]]]

console .log (transform (response))
.as-console-wrapper max-height: 100% !important; top: 0

【讨论】:

我非常喜欢这个。除了你、Nina/trincot 和我使用的三种不同的基础技术之外,一些实现细节的相似性最终并不令人惊讶。【参考方案4】:

一个递归实现的 reduceer 功能通常可以映射和收集任何项目。

它使用collector 对象作为reduce method's 2nd argument(以及reducer 的初始值)。 collectorresult 数组收集任何项目。并且count 不断增加并分配为收集项目的id(前DIAGID),而项目的parentId(前PARENTID)根据需要更新,以便始终反映当前的递归调用堆栈.. .

function countMapAndCollectNestedItemRecursively(collector, item) 

  let  count = 0, parentId = null, result  = collector;
  const  children = [], ...itemRest  = item;

  result.push(

    id: ++count,
    parentId,

    ...itemRest,
  );
  count = children.reduce(

    countMapAndCollectNestedItemRecursively,
     count, parentId: count, result 

  ).count;

  return  count, parentId, result ;


const sampleData = [
  label: "FOO",
  value: "foo",
  children: [

    label: "FOO BAR",
    value: "fooBar",
    children: [

      label: "FOO BAR BAZ",
      value: "fooBarBaz",
    ],
  , 
    label: "FOO BIZ",
    value: "fooBiz",
    children: [

      label: "FOO BIZ BUZ",
      value: "fooBizBuz",
    ],
  ],
, 
  label: "BAR",
  value: "bar",
  children: [

    label: "BAR BAZ",
    value: "barBaz",
    children: [

      label: "BAR BAZ BIZ",
      value: "barBazBiz",
      children: [

        label: "BAR BAZ BIZ BUZ",
        value: "barBazBizBuz",
      ],
    , 
      label: "BAR BAZ BUZ",
      value: "barBazBuz",
    ],
  , 
    label: "BAR BIZ",
    value: "barBiz",
    children: [

      label: "BAR BIZ BUZ",
      value: "barBizBuz",
    ],
  ],
];

console.log(
  sampleData.reduce(

    countMapAndCollectNestedItemRecursively,
     result: [] ,

  ).result
);
.as-console-wrapper  min-height: 100%!important; top: 0; 

【讨论】:

【参考方案5】:

这是一个递归函数dfs,它对输入树执行前序遍历,并传递一个计数器,该计数器提供将在输出中使用的id 属性。当前节点的id 也作为parentId 传递给递归调用:

const dfs = (children, counter=id: 1, parentId=null) =>
    children.flatMap((children=[], ...node) => [ 
        ...counter, 
        parentId,
        ...node
    ].concat(dfs(children, counter, counter.id++)));

const response = [label: "search me",value: "searchme",children: [label: "search me too",value: "searchmetoo",children: [label: "No one can get me",value: "anonymous",],],, label: "search me2",value: "searchme2",children: [label: "search me too2",value: "searchmetoo2",children: [label: "No one can get me2",value: "anonymous2",],],];
const result = dfs(response);
console.log(result);

【讨论】:

我内心的 FP 纯粹主义者对这个可变参数感到有些恶心,但是该死,这太狡猾了! 我不知道您的代码是否是对问题的早期编辑的回应,但输入是(现在)和您输入的项目数组。 是的,我的代码是针对问题的早期版本,其中输入不是数组。现在更新了(当然太晚了——忙碌的一天)。

以上是关于如何将树状的数组和对象的嵌套数据结构转换为具有计算/计数 id 和跟踪父 id 的项目列表?的主要内容,如果未能解决你的问题,请参考以下文章

如何将菊花链/点符号中的 JavaScript 对象展开为具有嵌套对象和数组的对象?

如何将嵌套数组对转换为数组中的对象

如何将嵌套对象转换为数组数组

如何将对象数组转换为嵌套对象?

将平面对象数组转换为嵌套对象数组[重复]

如何在javascript中将嵌套对象转换为对象数组? [关闭]