从数组 javascript 创建嵌套 object

Create nested object from array javascript

我有一个 SharePoint 列表,我正在通过 REST 在 javascript 中进行操作。我需要将数组从 REST 转换为嵌套 object。我正在构建一个基于结构的导航菜单。该数组由 ID、Title、Parent 和 Level 组成。级别是 Top、Mid 或 Bot,Parent 列出了条目的标题 parent。

let orgs = [{
    id: "0",
    title: "A",
    parent: "",
    level: "Top"
  },{
    id: "1",
    title: "Org A1",
    parent: "A",
    level: "Middle"
  },
  {
    id: "2",
    title: "Org A2",
    parent: "A",
    level: "Middle"
  },
  {
    id: "3",
    title: "Org A3",
    parent: "A",
    level: "Middle"
  },
  {
    id: "5",
    title: "Org A1-A",
    parent: "Org A1",
    level: "Bottom"
  },
  {
    id: "6",
    title: "Org A1-B",
    parent: "Org A1",
    level: "Bottom"
  },
  {
    id: "7",
    title: "Org A2-A",
    parent: "Org A2",
    level: "Bottom"
  },
  {
    id: "8",
    title: "Org A2-B",
    parent: "Org A2",
    level: "Bottom"
  },  
  {
    id: "9",
    title: "Org A3-A",
    parent: "Org A3",
    level: "Bottom"
  },
  {
    id: "10",
    title: "Org A3-B",
    parent: "Org A3",
    level: "Bottom"
  }
];

我正在尝试遍历数组并将 children 的标题添加到适当的 parent。 object 的目标结构是:

const orgObj = {
  Title: "Org A",
  mid: [{
    Title: "Org A1",
    bot: [{
      Title: "Org A1-A"
    }, {
      Title: "Org A1-B"
    }]
  }, {
    Title: "Org A2",
    bot: [{
      Title: "Org A2-A"
    }, {
      Title: "Org A2-B"
    }]
  }, {
    Title: "Org A3",
    bot: [{
      Title: "Org A3-A"
    }, {
      Title: "Org A3-B"
    }]
  }]
};

我正在尝试使用地图功能,但无法使结果正常工作。我断断续续地做了几天,似乎无法让它发挥作用。我无法访问我目前在当前位置尝试过的代码,但我会在可以获取时尝试更新。在我看来,这应该不复杂。

这是一种方法...

let orgs = [{
  id: "0",
  title: "Org A",
  parent: "",
  level: "Top"
},{
  id: "1",
  title: "Org A1",
  parent: "Org A",
  level: "Middle"
},
{
  id: "2",
  title: "Org A2",
  parent: "Org A",
  level: "Middle"
},
{
  id: "3",
  title: "Org A3",
  parent: "Org A",
  level: "Middle"
},
{
  id: "5",
  title: "Org A1-A",
  parent: "Org A1",
  level: "Bottom"
},
{
  id: "6",
  title: "Org A1-B",
  parent: "Org A1",
  level: "Bottom"
},
{
  id: "7",
  title: "Org A2-A",
  parent: "Org A2",
  level: "Bottom"
},
{
  id: "8",
  title: "Org A2-B",
  parent: "Org A2",
  level: "Bottom"
},  
{
  id: "9",
  title: "Org A3-A",
  parent: "Org A3",
  level: "Bottom"
},
{
  id: "10",
  title: "Org A3-B",
  parent: "Org A3",
  level: "Bottom"
}
];

let roots = {};

orgs.forEach(org => {
  if (org.parent) {
    let parent = orgs.find(o => o.title === org.parent)
    if (parent.level === 'Top') {
      if (!parent.mid) parent.mid = [];
      parent.mid.push(org);
    } else if (parent.level === 'Middle') {
      if (!parent.bot) parent.bot = [];
      parent.bot.push(org);
    }
  } else {
    roots[org.title] = org;
  }
})

console.log(JSON.stringify(Object.values(roots), null, 2));

您可以使用以下算法在线性时间内为您构建树 O(n)

我在这里大量使用 Maps 以保持运行时间线性。

首先,我创建了一个 Map,它将每个 title 映射到相应的数据。我稍后需要它,这样我就可以快速查找组织可能拥有的任何 parent 的数据。结果将是这样的:

Map(10) {
  'A' => { id: '0', title: 'A', parent: '', level: 'Top' },
  'Org A1' => { id: '1', title: 'Org A1', parent: 'A', level: 'Middle' },
  'Org A2' => { id: '2', title: 'Org A2', parent: 'A', level: 'Middle' },
  'Org A3' => { id: '3', title: 'Org A3', parent: 'A', level: 'Middle' },
  'Org A1-A' => { id: '5', title: 'Org A1-A', parent: 'Org A1', level: 'Bottom' },
  'Org A1-B' => { id: '6', title: 'Org A1-B', parent: 'Org A1', level: 'Bottom' },
  'Org A2-A' => { id: '7', title: 'Org A2-A', parent: 'Org A2', level: 'Bottom' },
  'Org A2-B' => { id: '8', title: 'Org A2-B', parent: 'Org A2', level: 'Bottom' },
  'Org A3-A' => { id: '9', title: 'Org A3-A', parent: 'Org A3', level: 'Bottom' },
  'Org A3-B' => { id: '10', title: 'Org A3-B', parent: 'Org A3', level: 'Bottom' }
}

然后我还在级别和道具名称之间创建一个映射,以说明您没有统一的 children 道具,但 midbot 取决于级别parent.

现在真正的树构造开始了。我再次使用 reduce() 遍历所有组织,并从一个空的 JavaScript object 开始。对于每个项目,我创建目标 object (newObj),它只包含 Title 并使用上面的 Map 获得 parent。如果没有 parent 这是 top-most 元素。

如果不是 parent,我可以使用我的 [=25] 在 orgChart object 中查找对 parent object 的引用=] Map。然后我有对树中 parent 节点的引用。现在我需要根据 parent.

的级别获取 children 属性 midbot 的名称

现在使用那个 属性 名称,我可以为 parent 节点分配这个新的 属性,值为新的 child object。如果 parent 已经有一个 child object 我可以简单地将另一个 child object 推到那个数组上,如果没有我需要创建一个新数组并推child object 到那个。

最后但同样重要的是,我需要将对新 object 的引用插入到我的 quickRefs Map 中,所以如果我以后遇到 object 这是一个现在插入的项目的 child 我可以直接将 children 分配给那个 object 如上所述。

const input = [
  {
    id: "0",
    title: "A",
    parent: "",
    level: "Top",
  },
  {
    id: "1",
    title: "Org A1",
    parent: "A",
    level: "Middle",
  },
  {
    id: "2",
    title: "Org A2",
    parent: "A",
    level: "Middle",
  },
  {
    id: "3",
    title: "Org A3",
    parent: "A",
    level: "Middle",
  },
  {
    id: "5",
    title: "Org A1-A",
    parent: "Org A1",
    level: "Bottom",
  },
  {
    id: "6",
    title: "Org A1-B",
    parent: "Org A1",
    level: "Bottom",
  },
  {
    id: "7",
    title: "Org A2-A",
    parent: "Org A2",
    level: "Bottom",
  },
  {
    id: "8",
    title: "Org A2-B",
    parent: "Org A2",
    level: "Bottom",
  },
  {
    id: "9",
    title: "Org A3-A",
    parent: "Org A3",
    level: "Bottom",
  },
  {
    id: "10",
    title: "Org A3-B",
    parent: "Org A3",
    level: "Bottom",
  },
];

// store titles to their data so we can find a parent object in constant time
const map = input.reduce((all, cur) => (all.set(cur.title, cur), all), new Map());

// mapping for the property names
// if the parent is level "Top", we need to use "mid"
// if the parent is level "Middle", we need to use "bot"
const mapLevelToPropName = {
  Top: "mid",
  Middle: "bot",
}

// stores references to objects within the object, so they can be accessed in constant time
const quickRefs = new Map();
const output = input.reduce((orgChart, org) => {
  const parent = map.get(org.parent);
  const newObj = {
    Title: org.title
  }
  if(parent === undefined) {
    // top most org
    orgChart = newObj
  }
  else {
    // some org in the middle which has a parent
    // get the reference to the parent within the JS object using the quickRefs map
    const refToParentObj = quickRefs.get(org.parent)
    // determine the name of the "children" property based on the level of the parent
    const propName = mapLevelToPropName[parent.level];
    // check if there already are childrens (mid, bot)
    // if yes: append another one to array
    if(refToParentObj[propName]) refToParentObj[propName].push(newObj);
    // if no: add new object within an array
    else refToParentObj[propName] = [newObj];
  }
  // add a reference to the new object into the quickRefs map so 
  // when we find a child we can just grab that reference and add the child 
  quickRefs.set(org.title, newObj);
  return orgChart;
}, {})

console.log(JSON.stringify(output, null, 2));
.as-console-wrapper { max-height: 100% !important; top: 0; }

This implementation assumes that child orgs only ever appear after parent orgs within the input array, but will work for deeply nested structures (although the "mid", "bot" functionality might not be suitable for this).