Это плоский объект, с которым я работаю, у него гораздо больше результатов, ~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 добавил то, как, по моему мнению, выглядит окончательный массив, и фотографию, чтобы было понятно, если у меня неправильный синтаксис.
Этот синтаксис действительно неверен. Если вы даже не можете выразить точный результат, который хотите, тогда трудно ответить. Я мог бы предположить, учитывая ваше изображение, что вам на самом деле не обязательно нужны массивы где-либо, кроме, может быть, на листьях, и даже тогда не очевидно, какими должны быть эти значения... и сделайте что-то вроде это. Если это соответствует вашим потребностям, я мог бы написать ответ. Если нет, то я настоятельно рекомендую выяснить, как записать желаемый результат в хорошо отформатированном JavaScript. Дайте мне знать, как действовать.
попался, так что вы были в значительной степени на деньгах, это точный результат, который я хочу, в разделе комментариев. tsplay.dev/mMB5QW Мне бы не понадобился [] в каталоге. Извините за это, обязательно сделаю больше исследований.
@jcalz, конечно, спасибо за помощь, по крайней мере, теперь я знаю, чему учиться в будущем!
@jcalz Очень похоже, я просто не хочу, чтобы все лишнее после catalogDetail, например, заканчивалось отображением строк внутри catalogDetail, например: tsplay.dev/NVbQZN если это невозможно, как вы сказали, я с этим точно можно работать
Я не понимаю. Разве вы не можете просто написать точное значение, которое вы хотите? Прямо сейчас это пустой массив в конце (потому что вы закомментировали содержимое); если вы хотите пустой массив я могу дать вам пустой массив . Вы хотите, чтобы последнее значение было не ключом, а пустой строкой в массиве? Если да могу подарить.
@jcalz Массив строк
Хорошо, я написал свой ответ. Возможно, вы захотите отредактировать вопрос, чтобы желаемый результат был именно таким, как я показал (или я могу сделать это, если хотите)
Я рекомендую вам использовать для этого метод 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
Эй, большое спасибо, посмотрю на это. Я буду великолепен
Похоже, для ввода-вывода вам нужно что-то вроде этого:
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"
]
}
}
}
} */
Это тоже работает!
@jcalz Привет, спасибо за отзыв! Я поставил классификацию 1 и 2, чтобы показать, что это будут разные классификации, например, 1 будет средним внедорожником, 2 будет средним автомобилем. Извините, я понимаю, как это было неясно. Там нет нескольких ключей классификации, просто разные типы значений внутри каждого из этих ключей. Я ищу вложенное дерево из 1-го примера, которое разбивает его от make, являющегося родительским узлом, до catalogdetail, являющегося конечным узлом. Извините, но все еще очень новичок в кодировании в целом и благодарю за помощь.