typescript 将平面对象转换为嵌套的树对象,如何用不同的值构建它?

eoigrqb6  于 2022-11-26  发布在  TypeScript
关注(0)|答案(3)|浏览(165)

这是我正在使用的平面对象,它有更多的结果,~6800。我一直试图将它转换为嵌套树(如下面列出的)大约13个小时了,我真的迷路了。

[
  {
    "make": "Acura",
    "classification": "Mid SUV",
    "segment": "Competitive Trucks",
    "model": "RDX",
    "catalogDetail": "RDX_SUV_4_Gasoline_2013_Base w/Tech_FWD_3.5_6_105.7_Automatic"
  },
  {
    "make": "Acura",
    "classification": "Midsize Car",
    "segment": "Competitive Cars",
    "model": "TSX",
    "catalogDetail": "TSX_Sedan_4_Gasoline_2012_Base w/Tech_FWD_2.4_4_106.4_Automatic"
  },
  {
    "make": "Aston Martin",
    "classification": "Compact Car",
    "segment": "Competitive Cars",
    "model": "DB11",
    "catalogDetail": "DB11_Convertible_2_Gasoline_2019_Volante_RWD_4.0_8_110.4_Automatic"
  }
]

我想做的是将这个平面对象构建成一个嵌套结构,如下所示:

[
  {
    "make": [ 
      { "Acura",
        "classification": [{
          "Mid SUV",
          "segment": [{
            "Competitive Trucks",
            "model": [{
              "RDX",
              "catalogDetail": [{
                "RDX_SUV_4_Gasoline_2013_Base w/Tech_FWD_3.5_6_105.7_Automatic"
              }]
            }]
          }],
          "Midsize Car",      
          "segment": [{
            "Competitive Cars",
            "model": [{
              "TSX",
              "catalogDetail": [{
                "TSX_Sedan_4_Gasoline_2012_Base w/Tech_FWD_2.4_4_106.4_Automatic"
              }]
            }]
          }] 
        }],
      }
    ]
  },
  {
    "make": [
      { "Aston Martin",
        "classification": [{
          "Compact Car",
          "segment": [{
            "Competitive Cars",
            "model": [{
              "DB11",
              "catalogDetail": [{
                "DB11_Convertible_2_Gasoline_2019_Volante_RWD_4.0_8_110.4_Automatic"
              }]
            }]
          }]
        }]
      }
    ]
  }
]

其中结构福尔斯嵌套结构,如:制造--〉分类--〉细分--〉型号--〉目录详细信息。因此,将有多个汽车制造商,福特、凯迪拉克等。多个分类,每个制造商下有多个不同的细分。
我试过了

this._servicesService.getHierarchy().subscribe(data => {
      console.log(data)
/*      this.data = data;*/
      /*      this.dataStore = data;*/

      let distinctSeg = [...new Set(data.map(x => x.segment))];
      let distinctClass = [...new Set(data.map(x => x.classification))];
      let distinctMod = [...new Set(data.map(x => x.model))];
      let distinctCd = [...new Set(data.map(x => x.catalogDetail))];

     const newData = [];
      data.forEach(e => {
        if (newData.length == 0) {
          newData.push({
            make: e.make,
            segment: e.segment,
            classification: e.classification,
            model: [e.model],
            catalogDetail: [e.catalogDetail]
          });
        } else {
          let foundIndex = newData.findIndex(fi => fi.make === e.make, fi => fi.segment = e.segment);
          if (foundIndex >= 0) {
            /* newData[foundIndex].make.push(e.make),*/
            /* newData[foundIndex].segment.push(e.segment),*/
            /* newData[foundIndex].classification.push(e.classification),*/
            newData[foundIndex].model.push(e.model);
            newData[foundIndex].catalogDetail.push(e.catalogDetail);
          } else {
            newData.push({
              make: e.make,
              segment: distinctSeg,
              classification: distinctClass,
              model: [e.model],
              catalogDetail: [e.catalogDetail]
            });
          }
        }
      });
      console.log(newData);
    })

这为模型、细分市场和类给予了不同的值,(由于某种原因,不是model或catalogDetail),但是嵌套结构并不存在,我真的不知道如何继续。我已经看了很多例子,我真的没有成功地应用前面列出的任何一条路线。任何见解或提示都将非常感谢。我附上了一张图片,以便在语法错误的情况下更好地可视化最终所需的输出。tree

plicqrtu

plicqrtu1#

对于输入-输出,看起来像这样:

const inp = [
  { a: "x", b: "u", c: "q" }, { a: "x", b: "v", c: "r" },
  { a: "x", b: "u", c: "s" }, { a: "x", b: "v", c: "t" },
  { a: "y", b: "u", c: "q" }, { a: "y", b: "u", c: "r" },
  { a: "y", b: "v", c: "s" },
];

const outp = collect(inp, "a", "b", "c");
console.log(outp);
// {x:{u: ["q","s"], v:["r","t"]}, y:{u:["q","r"], v:["s"]}}

其中collect()是一个函数,它接受一个对象数组和这些对象的键列表。(它至少需要一个键,并且输入对象在这些键上应该有string值。)我们的工作是实现collect()
我采用的方法是递归的;第一种是基本情况,只对一个键调用collect(inp, key1)。在这种情况下,我们只想返回一个包含key1键的值的数组,我们可以通过map ping得到输入数组;即inp.map(v => v[key1])
然后递归一步:当您使用多个键调用collect(inp, key1, ...keyRest)时,输出将具有与inp元素的key1属性相对应的键。在上面的示例中,如果我们调用collect(inp, "a", ...keyRest),则输出将具有键xy。对于x键,我们将inpa属性为"x"的元素收集到另一个数组inpX中,则x键处的值将为collect(inpX, ...keyRest)。类似地,对于y键。也就是说,我们将输入数组拆分为与key1键处的值相对应的子数组,然后对每个子数组求值collect(subArr, ...keyRest)
这是对算法的口头描述,让我们看看它对collect()typings 意味着什么:

declare function collect<K extends (keyof T)[], T extends object>(
  arr: (T & Record<K[number], string>)[], ...keys: K): Collect<K>;

type Collect<K extends PropertyKey[]> =
  K extends [any] ? string[] :
  K extends [any, ...infer R extends PropertyKey[]] ? { [k: string]: Collect<R> } :
  never;

这里我们说collect()是一个generic函数,它接受对象类型为T的元素数组和元组类型为K的键列表。我们对arr进行约束,使arr数组的每个元素都是T类型,并且在K中的每个关键元素处都具有string属性我们约束K,使得每个关键元素都是T的某个关键字。
我们返回Collect<K>类型的值,其中Collect<K>本身是一个递归条件类型,表示具有嵌套string索引签名的对象,其基本情况值类型为string[]
现在开始实施:

function collect(arr: any[], ...keys: string[]) {
  if (!keys.length) throw new Error("need at least one key");
  const [k, ...rest] = keys;

  // base case
  if (!rest.length) return arr.map(v => v[k]);

  // recurse; first collect the sub-arrays for each value at key k
  const subArrays: Record<string, any[]> = {}
  arr.forEach(v => (subArrays[v[k]] ??= []).push(v));

  // then build the return object by calling collect(subArrayVk, ...rest) for each subarray
  const ret: Record<string, any> = {};
  Object.keys(subArrays).forEach(vk => ret[vk] = collect(subArrays[vk], ...rest));
  return ret;
}

因为函数的调用签名返回一个通用的条件类型,所以最简单的方法是让函数成为一个单调用签名重载,这样就可以对实现进行宽松的检查。这意味着我们在实现之前有一个声明的调用签名:

// call signature
function collect<K extends (keyof T)[], T extends object>(
  arr: (T & Record<K[number], string>)[], ...keys: K): Collect<K>;

// implementation
function collect(arr: any[], ...keys: string[]) {
  // ✂ snip, see above
}

好吧,我们来测试一下:

const outp = collect(inp, "a", "b", "c");
console.log(outp);
// {x:{u: ["q","s"], v:["r","t"]}, y:{u:["q","r"], v:["s"]}}

这很有效!你的例子是:

const x = collect(arr, "make", "classification", "segment", "model", "catalogDetail");
console.log(x);
/* {
  "Acura": {
    "Mid SUV": {
      "Competitive Trucks": {
        "RDX": [
          "RDX_SUV_4_Gasoline_2013_Base w/Tech_FWD_3.5_6_105.7_Automatic"
        ]
      }
    },
    "Midsize Car": {
      "Competitive Cars": {
        "TSX": [
          "TSX_Sedan_4_Gasoline_2012_Base w/Tech_FWD_2.4_4_106.4_Automatic"
        ]
      }
    }
  },
  "Aston Martin": {
    "Compact Car": {
      "Competitive Cars": {
        "DB11": [
          "DB11_Convertible_2_Gasoline_2019_Volante_RWD_4.0_8_110.4_Automatic"
        ]
      }
    }
  }
}   */

那也行!
Playground代码链接

mo49yndu

mo49yndu2#

我建议您使用Array.reduce方法。
此方法允许您轻松地将JS数组转换为对象。
您可以执行以下操作:

arr.reduce((accumulator, currentValue)=>{
  if(!accumulator[currentValue.make]){
    accumulator[currentValue.make] = []
  }
  accumulator[currentValue.make][0] = {...<PUT YOUR OBJECT VALUE HERE>}
},{})
gg58donl

gg58donl3#

就像Avrham说的,你可以使用reduce。

reduce(data:any[],key:string,keyArray:string)
  {
    return data.reduce((a:any[],b:any)=>{
       const element=a.find(x=>x[key]==b[key])
       const value=b[key]
       delete b[key]
       if (!element)
          a.push({[key]:value,[keyArray]:[b]})
       else
          element[keyArray].push(b)
        return a;
    },[])
  }
}

您可以在以下方式中使用:

const data=this.reduce(this.data,"make","classification")
 data.forEach(x=>{
   x.classification=this.reduce(x.classification,"classification","segment")
   x.classification.forEach(c=>{
    c.segment=this.reduce(c.segment,"segment","model")
    c.segment.forEach(m=>{
       m.model=this.reduce(m.model,"model","catalogDetail")
       m.model.forEach(d=>d.catalogDetail=d.catalogDetail.map(e=>e.catalogDetail))
    })
   })
 })
 this.dataFormatted=data

但是我认为管理这种树视图并不容易。通常你有一个树视图,所有的数组都是“孩子”。所以我建议创建一个函数

reduceChildren(data:any[],key:string)
  {
    return data.reduce((a:any[],b:any)=>{
      const element=a.find(x=>x[key]==b[key])
      const value=b[key]
      delete b[key]
      if (!element)
         a.push({[key]:value,children:[b]})
      else
         element.children.push(b)
       return a;
   },[])
  }

您可以使用像

const data=this.reduceChildren(this.data,"make")
 data.forEach(x=>{
   x.children=this.reduceChildren(x.children,"classification")
   x.children.forEach(c=>{
    c.children=this.reduceChildren(c.children,"segment")
    c.children.forEach(m=>{
       m.children=this.reduceChildren(m.children,"model")
       m.children.forEach(d=>d.children=d.children.map(e=>e.catalogDetail))
    })
   })
 })
 this.dataFormatted=data

您可以在this stackblitz中看到这两种方法

相关问题