Плоский объект Typescript для вложенного объекта дерева, как построить его с различными значениями?

Это плоский объект, с которым я работаю, у него гораздо больше результатов, ~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"
              }]
            }]
          }]
        }]
      }
    ]
  }
]

Где структура попадает во вложенную структуру, например: марка --> классификация --> сегмент --> модель --> каталогдеталь. Таким образом, будет несколько марок автомобилей, Ford, Cadillac и т. д. Несколько классификаций, несколько разных сегментов по каждой марке.

Это то, что я пробовал:

    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);
    })

Это дает мне разные значения для модели, сегмента и класса (по какой-то причине не модель или catalogDetail), но вложенной структуры там нет, и я действительно не знаю, как действовать дальше. Я просмотрел несколько примеров здесь, и мне действительно не удалось применить ни один из ранее перечисленных маршрутов. Любая способность проникновения в суть или подсказки больш были бы оценены. Я прикрепил изображение, чтобы лучше визуализировать окончательный желаемый результат на случай, если у меня неправильный синтаксис. дерево

@jcalz Привет, спасибо за отзыв! Я поставил классификацию 1 и 2, чтобы показать, что это будут разные классификации, например, 1 будет средним внедорожником, 2 будет средним автомобилем. Извините, я понимаю, как это было неясно. Там нет нескольких ключей классификации, просто разные типы значений внутри каждого из этих ключей. Я ищу вложенное дерево из 1-го примера, которое разбивает его от make, являющегося родительским узлом, до catalogdetail, являющегося конечным узлом. Извините, но все еще очень новичок в кодировании в целом и благодарю за помощь.

Anony 20.11.2022 19:08

@jcalz добавил то, как, по моему мнению, выглядит окончательный массив, и фотографию, чтобы было понятно, если у меня неправильный синтаксис.

Anony 20.11.2022 20:08

Этот синтаксис действительно неверен. Если вы даже не можете выразить точный результат, который хотите, тогда трудно ответить. Я мог бы предположить, учитывая ваше изображение, что вам на самом деле не обязательно нужны массивы где-либо, кроме, может быть, на листьях, и даже тогда не очевидно, какими должны быть эти значения... и сделайте что-то вроде это. Если это соответствует вашим потребностям, я мог бы написать ответ. Если нет, то я настоятельно рекомендую выяснить, как записать желаемый результат в хорошо отформатированном JavaScript. Дайте мне знать, как действовать.

jcalz 20.11.2022 20:39

попался, так что вы были в значительной степени на деньгах, это точный результат, который я хочу, в разделе комментариев. tsplay.dev/mMB5QW Мне бы не понадобился [] в каталоге. Извините за это, обязательно сделаю больше исследований.

Anony 20.11.2022 20:45

@jcalz, конечно, спасибо за помощь, по крайней мере, теперь я знаю, чему учиться в будущем!

Anony 20.11.2022 20:57

@jcalz Очень похоже, я просто не хочу, чтобы все лишнее после catalogDetail, например, заканчивалось отображением строк внутри catalogDetail, например: tsplay.dev/NVbQZN если это невозможно, как вы сказали, я с этим точно можно работать

Anony 20.11.2022 23:18

Я не понимаю. Разве вы не можете просто написать точное значение, которое вы хотите? Прямо сейчас это пустой массив в конце (потому что вы закомментировали содержимое); если вы хотите пустой массив я могу дать вам пустой массив . Вы хотите, чтобы последнее значение было не ключом, а пустой строкой в ​​массиве? Если да могу подарить.

jcalz 20.11.2022 23:26

@jcalz Массив строк

Anony 21.11.2022 00:52

Хорошо, я написал свой ответ. Возможно, вы захотите отредактировать вопрос, чтобы желаемый результат был именно таким, как я показал (или я могу сделать это, если хотите)

jcalz 21.11.2022 01:37
Шаблоны Angular PrimeNg
Шаблоны Angular PrimeNg
Как привнести проверку типов в наши шаблоны Angular, использующие компоненты библиотеки PrimeNg, и настроить их отображение с помощью встроенной...
Promise и Observables в Angular
Promise и Observables в Angular
Здесь мы рассмотрим основные различия между обещаниями и наблюдаемыми таблицами на примере.
Как использовать d3.js для рисования 2D SVG-элементов в приложении Angular?
Как использовать d3.js для рисования 2D SVG-элементов в приложении Angular?
D3.js - это обширная библиотека, используемая для привязки произвольных данных к объектной модели документа (DOM). Мы разберем основные варианты...
Разработка Drupal и AngularJS: Идеальное сочетание для вашей веб-стратегии
Разработка Drupal и AngularJS: Идеальное сочетание для вашей веб-стратегии
Один опытный веб-разработчик назвал комбинацию Drupal и AngularJS "сочетанием, созданным на небесах". Почему так? Потому что вместе они могут создать...
Создание многостраничной формы заявления о приеме на работу с помощью Angular
Создание многостраничной формы заявления о приеме на работу с помощью Angular
Наличие на корпоративном сайте форм заявлений о приеме на работу, или "трудовых анкет", экономит время и деньги как для соискателей, так и для...
Добавление поддержки полноэкранного режима в Ng-Zorro
Добавление поддержки полноэкранного режима в Ng-Zorro
В эти дни я работаю над проектом и выбрал ng-zorro в качестве библиотеки компонентов для этой задачи.
1
9
90
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Я рекомендую вам использовать для этого метод Array.reduce.

Этот метод позволяет легко преобразовать массив js в объект.

Вы можете сделать что-то вроде следующего:

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

Как сказал Аврам, вы можете использовать сокращение. Представьте, что вы создаете функцию, например

  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

Вы можете увидеть два подхода в этом stackblitz

Эй, большое спасибо, посмотрю на это. Я буду великолепен

Anony 20.11.2022 20:21
Ответ принят как подходящий

Похоже, для ввода-вывода вам нужно что-то вроде этого:

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, который мы можем получить, просто сопоставив входной массив; то есть inp.map(v => v[key1]).

Затем рекурсивный шаг: когда вы вызываете collect(inp, key1, ...keyRest) с более чем одним ключом, на выходе будут ключи, соответствующие свойству key1 элементов inp. В приведенном выше примере, если мы вызовем collect(inp, "a", ...keyRest), то на выходе будут ключи x и y. Для ключа x мы собираем элементы inp, чье свойство a равно "x", в другой массив inpX, и тогда значение ключа x будет collect(inpX, ...keyRest). И аналогично для клавиши y. То есть мы разбиваем входной массив на подмассивы, соответствующие значениям в ключе key1, а затем оцениваем collect(subArr, ...keyRest) для каждого подмассива.


Это словесное описание алгоритма. Давайте посмотрим, что это означает для набора текста для collect():

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() — это универсальная функция, которая принимает массив элементов объектного типа T и список ключей кортежного типа K. Мы ограничиваемarr так, чтобы каждый элемент массива arr имел тип T и имел свойство string в каждом ключевом элементе кортежа K. И мы ограничиваем 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"
        ]
      }
    }
  }
}   */

Это тоже работает!

Площадка ссылка на код

Другие вопросы по теме