I am trying through the array and use a Array.prototype.filter() method on every children array to find the elements whose key matches with the ones specified.
Then, I'am using Array.prototype.splice() to remove the results from the respective children array but the result is return undefined.
const inputArray = [
"Oxygen-a3b8be32-c36e-a02e-37f4-a35239e0cedb",
"633ac872e78fa7ebee03b8bf",
"5e69dbd7-5fee-67a9-c73f-4656f9b90715",
"d484558b-4717-b0b8-db07-68288afb4f6a",
"63922aac4ff08f52d71fa891",
"33a3182b-93a4-84b9-4c49-c955a8416197",
];
const originalArray = [{
title: "Animals",
key: "d484558b-4717-b0b8-db07-68288afb4f6a",
children: [{
title: "Color",
key: "63922aac4ff08f52d71fa891",
children: [{
title: "Black",
key: "Black-9e994ed2-823b-d1d6-4613-91d43f570fec",
},
{
title: "White",
key: "White-5d0b102a-2555-8f7c-d471-cc82a5bd9c01",
},
],
}, ],
},
{
title: "Elements",
key: "5e69dbd7-5fee-67a9-c73f-4656f9b90715",
children: [{
title: "Non metals",
key: "633ac872e78fa7ebee03b8bf",
children: [{
title: "Carbon",
key: "Carbon-e443daa4-def4-9830-796e-ee8c5a1f41d4",
},
{
title: "Nitrogen",
key: "Nitrogen-c2922569-0b2d-0e07-454d-d8411af701b7",
},
{
title: "Oxygen",
key: "Oxygen-a3b8be32-c36e-a02e-37f4-a35239e0cedb",
},
],
}, ],
},
{
title: "Planets",
key: "33a3182b-93a4-84b9-4c49-c955a8416197",
children: [{
title: "Composition",
key: "63b3d5cd12c06ba7ce353f76",
children: [{
title: "Chthonian planet",
key: "Chthonian planet-b3c593c1-d29e-5e14-1b11-2241e8ef2be6",
},
{
title: "Carbon planet",
key: "Carbon planet-07d67d62-afcf-fbcf-a8e8-75081cb44c2f",
},
],
}, ],
},
];
console.log(
"🚀 ~ file: TranferTree.misc.js:152 ~ onCheck ~ outputArray",
originalArray.forEach(e => {
e.children.forEach((c, i) => {
if (inputArray.includes(c.key)) {
e.children.splice(i, 1);
} else {
c.children.forEach((cc, j) => {
if (inputArray.includes(cc.key)) {
c.children.splice(j, 1);
}
});
}
});
})
);
Note: For example in the Elements => 5e69dbd7-5fee-67a9-c73f-4656f9b90715 children Non metals => 633ac872e78fa7ebee03b8bf i am only remove object with this key => Oxygen-a3b8be32-c36e-a02e-37f4-a35239e0cedb I want to keep the other objects that were not found this also applies to for example Composition => 63b3d5cd12c06ba7ce353f76 or Planets => 33a3182b-93a4-84b9-4c49-c955a8416197.
Since you want to preserve the original object references it will be slightly less efficient, but here's a way you can do it with recursive function calls. It provides the same output as your code, but it's correctly logging the final structure whereas yours is logging the return value of .forEach() which is undefined, by design.
const inputArray = [
"Oxygen-a3b8be32-c36e-a02e-37f4-a35239e0cedb",
"633ac872e78fa7ebee03b8bf",
"5e69dbd7-5fee-67a9-c73f-4656f9b90715",
"d484558b-4717-b0b8-db07-68288afb4f6a",
"63922aac4ff08f52d71fa891",
"33a3182b-93a4-84b9-4c49-c955a8416197",
];
const originalArray = [{
title: "Animals",
key: "d484558b-4717-b0b8-db07-68288afb4f6a",
children: [{
title: "Color",
key: "63922aac4ff08f52d71fa891",
children: [{
title: "Black",
key: "Black-9e994ed2-823b-d1d6-4613-91d43f570fec",
},
{
title: "White",
key: "White-5d0b102a-2555-8f7c-d471-cc82a5bd9c01",
},
],
}, ],
},
{
title: "Elements",
key: "5e69dbd7-5fee-67a9-c73f-4656f9b90715",
children: [{
title: "Non metals",
key: "633ac872e78fa7ebee03b8bf",
children: [{
title: "Carbon",
key: "Carbon-e443daa4-def4-9830-796e-ee8c5a1f41d4",
},
{
title: "Nitrogen",
key: "Nitrogen-c2922569-0b2d-0e07-454d-d8411af701b7",
},
{
title: "Oxygen",
key: "Oxygen-a3b8be32-c36e-a02e-37f4-a35239e0cedb",
},
],
}, ],
},
{
title: "Planets",
key: "33a3182b-93a4-84b9-4c49-c955a8416197",
children: [{
title: "Composition",
key: "63b3d5cd12c06ba7ce353f76",
children: [{
title: "Chthonian planet",
key: "Chthonian planet-b3c593c1-d29e-5e14-1b11-2241e8ef2be6",
},
{
title: "Carbon planet",
key: "Carbon planet-07d67d62-afcf-fbcf-a8e8-75081cb44c2f",
},
],
}, ],
},
];
function filterChildrenById (item, ids) {
if (item.children) {
for (let i = 0; i < item.children.length; i++) {
let child = item.children[i];
if (ids.includes(child.key)) {
item.children.splice(i, 1);
// Reduce index because we removed an item so indexing will
// be off if we don't do this
i--;
} else if (Array.isArray(child.children)) {
child = filterChildrenById(child, ids);
}
}
}
return item;
}
function filterData(data, ids) {
data.forEach(item => filterChildrenById(item, ids))
return data;
}
console.log(
"🚀 ~ file: TranferTree.misc.js:152 ~ onCheck ~ outputArray",
filterData(originalArray, inputArray)
);
You need to iterate from the end of the array, because splice changes index for the followind item.
const
keys = ["Oxygen-a3b8be32-c36e-a02e-37f4-a35239e0cedb", "633ac872e78fa7ebee03b8bf", "5e69dbd7-5fee-67a9-c73f-4656f9b90715", "d484558b-4717-b0b8-db07-68288afb4f6a", "63922aac4ff08f52d71fa891", "33a3182b-93a4-84b9-4c49-c955a8416197"],
data = [{ title: "Animals", key: "d484558b-4717-b0b8-db07-68288afb4f6a", children: [{ title: "Color", key: "63922aac4ff08f52d71fa891", children: [{ title: "Black", key: "Black-9e994ed2-823b-d1d6-4613-91d43f570fec" }, { title: "White", key: "White-5d0b102a-2555-8f7c-d471-cc82a5bd9c01" }] }] }, { title: "Elements", key: "5e69dbd7-5fee-67a9-c73f-4656f9b90715", children: [{ title: "Non metals", key: "633ac872e78fa7ebee03b8bf", children: [{ title: "Carbon", key: "Carbon-e443daa4-def4-9830-796e-ee8c5a1f41d4" }, { title: "Nitrogen", key: "Nitrogen-c2922569-0b2d-0e07-454d-d8411af701b7" }, { title: "Oxygen", key: "Oxygen-a3b8be32-c36e-a02e-37f4-a35239e0cedb" }] }] }, { title: "Planets", key: "33a3182b-93a4-84b9-4c49-c955a8416197", children: [{ title: "Composition", key: "63b3d5cd12c06ba7ce353f76", children: [{ title: "Chthonian planet", key: "Chthonian planet-b3c593c1-d29e-5e14-1b11-2241e8ef2be6" }, { title: "Carbon planet", key: "Carbon planet-07d67d62-afcf-fbcf-a8e8-75081cb44c2f" }] }] }],
remove = keys => {
const fn = array => {
let i = array.length;
while (i--) {
if (keys.includes(array[i].key)) array.splice(i, 1);
else if (array[i].children) fn(array[i].children);
}
};
return fn;
};
remove(keys)(data);
console.log(data);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Related
const data = [
{
title: '0-0',
key: '0-0',
children: [
{
title: '0-0-0',
key: '0-0-0',
children: [
{ title: '0-0-0-0', key: '0-0-0-0' },
{ title: '0-0-0-1', key: '0-0-0-1' },
{ title: '0-0-0-2', key: '0-0-0-2' },
],
},
{
title: '0-0-1',
key: '0-0-1',
children: [
{ title: '0-0-1-0', key: '0-0-1-0' },
{ title: '0-0-1-1', key: '0-0-1-1' },
{ title: '0-0-1-2', key: '0-0-1-2' },
],
},
{
title: '0-0-2',
key: '0-0-2',
},
],
},
{
title: '0-1',
key: '0-1',
children: [
{ title: '0-1-0-0', key: '0-1-0-0' },
{ title: '0-1-0-1', key: '0-1-0-1' },
{ title: '0-1-0-2', key: '0-1-0-2' },
],
},
{
title: '0-2',
key: '0-2',
},
];
How would I get an array of all values throughout all nests of this obj by the key of id.
For example
input: ["0-0-0"]
i wanna output like this
output: ["0-0-0", "0-0-0-0", "0-0-0-1", "0-0-0-2"]
enter image description here
You can recursively loop over all the children and get the keys that either match one of the target keys or if any of their ancestors have matched one the target keys.
const data = [
{
title: "0-0",
key: "0-0",
children: [
{
title: "0-0-0",
key: "0-0-0",
children: [
{ title: "0-0-0-0", key: "0-0-0-0" },
{ title: "0-0-0-1", key: "0-0-0-1" },
{ title: "0-0-0-2", key: "0-0-0-2" },
],
},
{
title: "0-0-1",
key: "0-0-1",
children: [
{ title: "0-0-1-0", key: "0-0-1-0" },
{ title: "0-0-1-1", key: "0-0-1-1" },
{ title: "0-0-1-2", key: "0-0-1-2" },
],
},
{
title: "0-0-2",
key: "0-0-2",
},
],
},
{
title: "0-1",
key: "0-1",
children: [
{ title: "0-1-0-0", key: "0-1-0-0" },
{ title: "0-1-0-1", key: "0-1-0-1" },
{ title: "0-1-0-2", key: "0-1-0-2" },
],
},
{
title: "0-2",
key: "0-2",
},
];
function getKeys(data, targetKeys) {
const targetKeysSet = new Set(targetKeys);
const outputKeys = [];
function getKeysHelper(data, hasParentMatched = false) {
data?.forEach((d) => {
if (targetKeysSet.has(d.key) || hasParentMatched) {
outputKeys.push(d.key);
getKeysHelper(d.children, true);
} else {
getKeysHelper(d.children);
}
});
}
getKeysHelper(data);
return outputKeys;
}
getKeys(data, ["0-0-0"]);
Relevant documentations:
Optional chaining (?.)
Array.prototype.includes
Set
you can do something like this
const extractKeys = data => {
const loop = (data, res) => {
if(!data.children){
return [...res, data.key]
}
return data.children.flatMap(d => loop(d, [...res, data.key]))
}
return [...new Set(loop(data, []))]
}
const findKeys = (data, keys) => data.flatMap(extractKeys).filter(k => keys.some(key => k.includes(key)))
const data = [
{
title: '0-0',
key: '0-0',
children: [
{
title: '0-0-0',
key: '0-0-0',
children: [
{ title: '0-0-0-0', key: '0-0-0-0' },
{ title: '0-0-0-1', key: '0-0-0-1' },
{ title: '0-0-0-2', key: '0-0-0-2' },
],
},
{
title: '0-0-1',
key: '0-0-1',
children: [
{ title: '0-0-1-0', key: '0-0-1-0' },
{ title: '0-0-1-1', key: '0-0-1-1' },
{ title: '0-0-1-2', key: '0-0-1-2' },
],
},
{
title: '0-0-2',
key: '0-0-2',
},
],
},
{
title: '0-1',
key: '0-1',
children: [
{ title: '0-1-0-0', key: '0-1-0-0' },
{ title: '0-1-0-1', key: '0-1-0-1' },
{ title: '0-1-0-2', key: '0-1-0-2' },
],
},
{
title: '0-2',
key: '0-2',
},
];
console.log(findKeys(data, ['0-0-0']))
How do I filter nested objects in React? Currently my function is only looking for the main title, not the nested ones.
export const treeData = [
{
title: 'Orange',
key: '0-0',
children: [
{
title: 'Anu',
key: '0-0-0',
isLeaf: true,
},
{
title: 'Anurag',
key: '0-0-1',
isLeaf: true,
},
],
},
{
title: 'ABC',
key: '0-1',
children: [
{
title: 'leaf 1-0',
key: '0-1-0',
isLeaf: true,
},
{
title: 'leaf 1-1',
key: '0-1-1',
isLeaf: true,
},
],
},
]
export default {
treeData,
}
and the function is:
const filtered = treeData.filter((data) => {
return data.title.toLowerCase().includes(e.target.value.toLowerCase())
})
So currently it is only searching the Orange and ABC, not the titles from children.
I recommend flattening the title values and then filtering. Note, the end result will contain a mixture of top level "parent" data and children.
const flattened = treeData.reduce((acc, data) => {
acc.push(data, ...data.children)
return acc;
}, []);
const filtered = flattened.filter((data) => {
return data.title.toLowerCase().includes(e.target.value.toLowerCase())
})
If you only need a filtered list of top level data, you will need to do something like the following:
function dataIncludesTitle(data, title) {
return data.title.toLowerCase().includes(title);
}
const searchValue = e.target.value.toLowerCase();
const filtered = treeData.filter((data) => {
return dataIncludesTitle(data, searchValue) || data.children.some(child => dataIncludesTitle(child, searchValue));
})
Something like:
const filtered = treeData.filter((data) => {
return data.title.toLowerCase().includes(e.target.value.toLowerCase()) ||
data.children.filter(x => x.title.toLowerCase().includes(e.target.value.toLowerCase())).length > 0
})
Look into data.children filtering subobject's title that contains e.target.value.toLowerCase().
If you want to get full objects containing child objects with a title similar to the search string, try this:
const treeData = [
{
title: 'Orange',
key: '0-0',
children: [
{
title: 'Anu',
key: '0-0-0',
isLeaf: true,
},
{
title: 'Anurag',
key: '0-0-1',
isLeaf: true,
},
],
},
{
title: 'ABC',
key: '0-1',
children: [
{
title: 'leaf 1-0',
key: '0-1-0',
isLeaf: true,
},
{
title: 'leaf 1-1',
key: '0-1-1',
isLeaf: true,
},
],
},
];
const searchString = 'ANUR';
const filtered = treeData.filter((datum) => {
const filteredChilds = datum.children.filter((child) => child.title.toLowerCase().includes(searchString.toLowerCase()));
return filteredChilds.length > 0;
});
console.log(filtered);
If you want to get these objects and only matched child objects try this:
const treeData = [{
title: 'Orange',
key: '0-0',
children: [{
title: 'Anu',
key: '0-0-0',
isLeaf: true,
},
{
title: 'Anurag',
key: '0-0-1',
isLeaf: true,
},
],
},
{
title: 'ABC',
key: '0-1',
children: [{
title: 'leaf 1-0',
key: '0-1-0',
isLeaf: true,
},
{
title: 'leaf 1-1',
key: '0-1-1',
isLeaf: true,
},
],
},
];
const searchString = 'ANUR';
const filtered = treeData.map((datum) => {
const filteredChilds = datum.children.filter((child) => child.title.toLowerCase().includes(searchString.toLowerCase()));
return {
...datum,
children: filteredChilds
};
}).filter(datum => datum.children.length > 0);
console.log(filtered);
If you want to get only matched child objects try this:
const treeData = [{
title: 'Orange',
key: '0-0',
children: [{
title: 'Anu',
key: '0-0-0',
isLeaf: true,
},
{
title: 'Anurag',
key: '0-0-1',
isLeaf: true,
},
],
},
{
title: 'ABC',
key: '0-1',
children: [{
title: 'leaf 1-0',
key: '0-1-0',
isLeaf: true,
},
{
title: 'leaf 1-1',
key: '0-1-1',
isLeaf: true,
},
],
},
];
const searchString = 'ANUR';
const filtered = treeData.reduce((acc, datum) => acc.concat(datum.children.filter((child) => child.title.toLowerCase().includes(searchString.toLowerCase()))), []);
console.log(filtered);
I have an array of objects that I want to filter by comparing a nested property to a search term.
For example:
let array = [
{
category: 15,
label: "Components",
value: "a614741f-7d4b-4b33-91b7-89a0ef96a099",
children: [
{
category: 1,
label: "Carousel1",
diId: 55946,
// as you can see there are many children nested array of object
children: [{ label: "nodatafoundmessage", value: "47d18fb2-3e63-4542-ad0e-e5e09acb5016", children: [] }],
value: "be5e027b-9163-4cfb-8816-0c8e3b816086"
},
{
category: 2,
label: "Checkbox1",
diId: 193909,
children: [{ label: "datafound", value: "47d18sb2-3e63-4542-ad0e-e5e09acb5016", children: [] }],
value: "045e8786-2165-4e1e-a839-99b1b0ceef57"
}
]
},
{
value: "4be22726-850c-4905-ab3b-039fcf607d55",
label: "Default",
children: [
{
category: 5,
defaultValueType: 1,
label: "Empty",
toType: "String",
value: "ebedb43f-4c53-491f-8954-d030321845cd"
},
{
category: 5,
defaultValueType: 2,
label: "Space",
toType: "String",
value: "2d0e1429-572b-4f21-9f83-3340bafff95a"
},
{
category: 5,
defaultValueType: 8,
label: "Current Username",
toType: "String",
value: "25f6b40a-33c7-4f17-b29d-99e8d1e4e33c"
},
{
category: 5,
defaultValueType: 9,
label: "Current Location",
toType: "Location",
value: "ed59da2f-318d-4599-9085-4d9d769a27d7"
}
]
},
{
category: 4,
label: "Fixed Value",
isFixed: true,
value: "28e90e3e-a20b-4499-9593-061a7d1e7bd6"
// as you can see there is no children in this object
}
]};
What I'm trying to achieve is if I search for 'nodata' for example my result should be
let array = [
{
category: 15,
label: "Components",
value: "a614741f-7d4b-4b33-91b7-89a0ef96a099",
children: [
{
category: 1,
label: "Carousel1",
diId: 55946,
// as you can see there are many children nested array of object
children: [{ label: "nodatafoundmessage", value: "47d18fb2-3e63-4542-ad0e-e5e09acb5016", children: [] }],
value: "be5e027b-9163-4cfb-8816-0c8e3b816086"
}
]
}
];
Another option if I search for 'spa' my result should be
let array = [
{
value: "4be22726-850c-4905-ab3b-039fcf607d55",
label: "Default",
children: [
{
category: 5,
defaultValueType: 2,
label: "Space",
toType: "String",
value: "2d0e1429-572b-4f21-9f83-3340bafff95a"
}
]
}
];
I have been super confused and I decided to get some help. Thank you for your helps guys!
The following function should do the trick for you:
function searchData(dataArray, searchTerm) {
return dataArray.flatMap(obj => {
const objHasSearchTerm = Object.entries(obj)
.some(([key, value]) => key !== 'children' && String(value).toLowerCase().includes(searchTerm.toLowerCase()));
if (objHasSearchTerm && !obj.children) return [obj];
const matchedChildren = searchData(obj.children ?? [], searchTerm);
return objHasSearchTerm || matchedChildren.length > 0
? [{
...obj,
children: matchedChildren,
}]
: [];
})
}
It recursively goes through the data array, looks for any entries that have the specified search term, and if so, places it into the newly constructed object. It will preserve the nested shape of the object, which may or may not be what is needed. Feel free to tweak the algorithm to your own needs.
let allData = [
{
category: 15,
label: "Components",
value: "a614741f-7d4b-4b33-91b7-89a0ef96a099",
children: [
{
category: 1,
label: "Carousel1",
diId: 55946,
// as you can see there are many children nested array of object
children: [{ label: "nodatafoundmessage", value: "47d18fb2-3e63-4542-ad0e-e5e09acb5016", children: [] }],
value: "be5e027b-9163-4cfb-8816-0c8e3b816086"
},
{
category: 2,
label: "Checkbox1",
diId: 193909,
children: [{ label: "datafound", value: "47d18sb2-3e63-4542-ad0e-e5e09acb5016", children: [] }],
value: "045e8786-2165-4e1e-a839-99b1b0ceef57"
}
]
},
{
value: "4be22726-850c-4905-ab3b-039fcf607d55",
label: "Default",
children: [
{
category: 5,
defaultValueType: 1,
label: "Empty",
toType: "String",
value: "ebedb43f-4c53-491f-8954-d030321845cd"
},
{
category: 5,
defaultValueType: 2,
label: "Space",
toType: "String",
value: "2d0e1429-572b-4f21-9f83-3340bafff95a"
},
{
category: 5,
defaultValueType: 8,
label: "Current Username",
toType: "String",
value: "25f6b40a-33c7-4f17-b29d-99e8d1e4e33c"
},
{
category: 5,
defaultValueType: 9,
label: "Current Location",
toType: "Location",
value: "ed59da2f-318d-4599-9085-4d9d769a27d7"
}
]
},
{
category: 4,
label: "Fixed Value",
isFixed: true,
value: "28e90e3e-a20b-4499-9593-061a7d1e7bd6"
// as you can see there is no children in this object
}
];
function searchData(dataArray, searchTerm) {
return dataArray.flatMap(obj => {
const objHasSearchTerm = Object.entries(obj)
.some(([key, value]) => key !== 'children' && String(value).toLowerCase().includes(searchTerm.toLowerCase()));
if (objHasSearchTerm && !obj.children) return [obj];
const matchedChildren = searchData(obj.children ?? [], searchTerm);
return objHasSearchTerm || matchedChildren.length > 0
? [{
...obj,
children: matchedChildren,
}]
: [];
})
}
console.log('----- Search: nodata')
console.log(JSON.stringify(searchData(allData, 'nodata'), null, 2))
console.log('----- Search: spa')
console.log(JSON.stringify(searchData(allData, 'spa'), null, 2))
I have the following array of object
const skus = [
{
id: 1,
features: ["Slim"],
fields: [
{ label: "Material", value: "Material1" },
{ label: "Type", value: "Type1" }
]
},
{
id: 2,
features: ["Cotton"],
fields: [
{ label: "Material", value: "Material2" },
{ label: "Type", value: "Type2" }
]
},
{
id: 3,
features: ["Slim"],
fields: [
{ label: "Material", value: "Material3" },
{ label: "Type", value: "Type1" }
]
}
]
And i want the expected output to be
const output = [
{ label: "features", value: ["Slim", "Cotton"] },
{ label: "Material", value: ["Material1", "Material2", "Material3"] },
{ label: "Type", value: ["Type1", "Type2"] }
]
I tried the following way
const output = [];
let featureArr = [];
let fieldsArr = []
skus.forEach(e => {
e.features.forEach(f => {
featureArr.push(f);
});
e.fields.forEach(f => {
fieldsArr.push({ label: f.label, value: f.value });
});
});
featureArr = _.uniq(featureArr);
fieldsArr = _.uniqBy(fieldsArr, 'value')
fieldsArr = _.groupBy(fieldsArr, 'label');
output.push({ label: 'Features', value: featureArr })
for (const k in fieldsArr) {
let valArr = []
valArr = fieldsArr[k].map(v => v.value)
output.push({ label: k, value: valArr });
}
I'm getting the expected output, but here multiple loops are present. Is there a way on how can i write the solution in more optimized way.
You could take a grouping function for nested properties, where a map, an array for iterating, group and value keys are handed over. The result is a map with all collected values for each group.
Later get all unique values from the map and build a new array of objects.
const
skus = [{ id: 1, features: ["Slim"], fields: [{ label: "Material", value: "Material1" }, { label: "Type", value: "Type1" }] }, { id: 2, features: ["Cotton"], fields: [{ label: "Material", value: "Material2" }, { label: "Type", value: "Type2" }] }, { id: 3, features: ["Slim"], fields: [{ label: "Material", value: "Material3" }, { label: "Type", value: "Type1" }] }],
getGrouped = (map, array, key, value) => array.reduce((m, o) =>
m.set(o[key], [...(m.get(o[key]) || []), o[value]]), map),
result = Array.from(
skus.reduce((m, o) =>
getGrouped(
m.set('features', [...(m.get('features') || []), ...o.features]),
o.fields,
'label',
'value'
),
new Map
),
([label, value]) => ({ label, value: [...new Set(value)] })
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
First Build an object with values as Sets. Then convert the object of sets into array of array.
const skus = [
{
id: 1,
features: ["Slim"],
fields: [
{ label: "Material", value: "Material1" },
{ label: "Type", value: "Type1" }
]
},
{
id: 2,
features: ["Cotton"],
fields: [
{ label: "Material", value: "Material2" },
{ label: "Type", value: "Type2" }
]
},
{
id: 3,
features: ["Slim"],
fields: [
{ label: "Material", value: "Material3" },
{ label: "Type", value: "Type1" }
]
}
];
const update = data => {
const res = {};
data.forEach(item => {
const features = res["features"] || new Set();
item.features.forEach(fea => features.add(fea));
res["features"] = features;
item.fields.forEach(field => {
const labels = res[field.label] || new Set();
labels.add(field.value);
res[field.label] = labels;
});
});
return Object.keys(res).map(key => ({ label: key, value: [...res[key]] }));
};
console.log(update(skus));
If you can use them, Sets will be your friend here:
//data
const skus = [{id: 1,features: ["Slim"],fields: [{ label: "Material", value: "Material1" },{ label: "Type", value: "Type1" }]},{id: 2,features: ["Cotton"],fields: [{ label: "Material", value: "Material2" },{ label: "Type", value: "Type2" }]},{id: 3,features: ["Slim"],fields: [{ label: "Material", value: "Material3" },{ label: "Type", value: "Type1" }]}];
//solution
const output = Object.entries(skus.reduce((map,sku) => {
sku.features.forEach(feat => map.features.add(feat));
sku.fields.forEach(field => (map[field.label] = (map[field.label] || new Set()).add(field.value)));
return map;
}, {features: new Set()})).map(([label, set]) => ({label, value: Array.from(set)}));
//display
console.log(output);
Each feature array and field array only get iterated exactly once using this approach.
If you can't use Sets, you can emulate their behavior using js objects. The goal is to use some structure that doesn't need to be iterated again to find unique values.
The following function will do the job
const fn = (array) => {
return array.reduce((result, element) => {
const features = result[0].value
const feature = element.features[0]
if (!features.includes(feature)) {
features.push(feature)
}
const materials = result[1].value
const material = element.fields[0].value
if (!materials.includes(material)) {
materials.push(material)
}
const types = result[2].value
const type = element.fields[1].value
if (!types.includes(type)) {
types.push(type)
}
return result
}, [
{ label: 'features', value: [] },
{ label: 'Material', value: [] },
{ label: 'Type', value: [] }
])
}
BUT, your object structure is quite messy, you should likely build accessor functions that extract information from your initial elements, and use some helper functions to populate your result object.
Anyway, read more about the 'reduce' function used here ;)
https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Array/reduce
My problem here builds upon another problem I was trying to solve and received an excellent answer for where I have a tree:
const treeData = [{
title: '0-0',
key: '0-0',
children: [{
title: '0-0-0',
key: '0-0-0',
children: [
{ title: '0-0-0-0', key: '0-0-0-0', children: [] },
{ title: '0-0-0-1', key: '0-0-0-1', children: [] },
{ title: '0-0-0-2', key: '0-0-0-2', children: [] },
],
}, {
title: '0-0-1',
key: '0-0-1',
children: [
{ title: '0-0-1-0', key: '0-0-1-0', children: [] },
{ title: '0-0-1-1', key: '0-0-1-1', children: [] },
{ title: '0-0-1-2', key: '0-0-1-2', children: [] },
],
}, {
title: '0-0-2',
key: '0-0-2',
children: []
}],
}, {
title: '0-1',
key: '0-1',
children: [
{ title: '0-1-0-0', key: '0-1-0-0', children: [] },
{ title: '0-1-0-1', key: '0-1-0-1', children: [] },
{ title: '0-1-0-2', key: '0-1-0-2', children: [] },
],
}, {
title: '0-2',
key: '0-2',
children: []
}];
and an array of leaf nodes:
const leafNodes = ['0-0-1-2', '0-1-0-1', '0-1-0-2']
Before, I just wanted a filtered/pruned copy of the tree that contains all the paths to the leaf nodes, but now I would like to further prune it by removing the parent node that doesn't satisfy a test -- the test being having all of its children included in the list of leaf nodes. The resulting tree would look like this:
const pruned = [{
title: '0-0-1-2',
key: '0-0-1-2',
children: []
},
{
title: '0-1-0-1',
key: '0-1-0-1',
children: []
}, {
title: '0-1-0-2',
key: '0-1-0-2',
children: []
}
]
Here, the node with keys 0-0-1 would be removed because only one of its 3 child nodes (0-0-1-2) is included in the leafNodes list and the child nodes included in the leaf nodes list (in this case, just the one) are bumped up to the level of their now removed parent. This would flow back up to the parent of the removed node, now with key 0-0, since not all of its children are included in the pruned tree.
This same pattern would apply to 0-1.
You could iterate the array and check if the child are complete selected , then get the actual node or just some children, then take the children only.
function getShort(array, keys) {
var result = [],
every = true;
array.forEach(o => {
var children;
if (keys.includes(o.key)) return result.push(o);
if (!o.children || !o.children.length) return every = false;
children = getShort(o.children, keys);
if (children.length && children.length === o.children.length) return result.push(o);
result.push(...children);
every = false;
});
return every
? array
: result;
}
const
treeData = [{ key: '0-0', children: [{ key: '0-0-0', children: [{ key: '0-0-0-0', children: [] }, { key: '0-0-0-1', children: [] }, { key: '0-0-0-2', children: [] }] }, { key: '0-0-1', children: [{ key: '0-0-1-0', children: [] }, { key: '0-0-1-1', children: [] }, { key: '0-0-1-2', children: [] }] }, { key: '0-0-2', children: [] }] }, { key: '0-1', children: [{ key: '0-1-0-0', children: [] }, { key: '0-1-0-1', children: [] }, { key: '0-1-0-2', children: [] }] }, { key: '0-2', children: [] }],
leafNodes = [
'0-0-0-0', '0-0-0-1', '0-0-0-2', // all 0-0-0 all 0-0
'0-0-1-0', '0-0-1-1', '0-0-1-2', // all 0-0-1 all 0-0
'0-0-2', // all 0-0-2 all 0-0
'0-1-0-1', '0-1-0-2'
],
short = getShort(treeData, leafNodes);
console.log(short);
.as-console-wrapper { max-height: 100% !important; top: 0; }