merge javascript array values with same key - javascript

What is the best way to re-organize array into output? I need to merge the values with the same key into an array of objects.
the input array:
var array = [
{
aaa: {
name: "foo1",
value: "val1"
}
},
{
aaa: {
name: "foo2",
value: "val2"
}
},
{
bbb: {
name: "foo3",
value: "val3"
}
},
{
bbb: {
name: "foo4",
value: "val4"
}
}
];
The desired output:
var output = [
{
aaa:
[{
name: "foo1",
value: "val1"
},{
name: "foo2",
value: "val2"
}]
},
{
bbb:
[{
name: "foo3",
value: "val3"
},{
name: "foo4",
value: "val4"
}]
}
];
What would be the best way to achieve this?
Thanks in advance.

You can use reduce
var array = [{aaa: {name: "foo1",value: "val1"}},{aaa: {name: "foo2",value: "val2"}},{bbb: {name: "foo3",value: "val3"}},{bbb: {name: "foo4",value: "val4"}}];
var newArray = array.reduce((c, v) => {
for (var k in v) {
c[k] = c[k] || [];
c[k].push(v[k]);
}
return c;
}, {});
console.log(newArray);
Doc: reduce()

You can use reduce and forEach function.
var array = [{ aaa: { name: "foo1", value: "val1" } }, { aaa: { name: "foo2", value: "val2" } }, { bbb: { name: "foo3", value: "val3" } }, { bbb: { name: "foo4", value: "val4" } }];
var result = array.reduce((a, c) => {
Object.keys(c).forEach((k) => {
if (a[k]) {
a[k].push(c[k]);
} else {
a[k] = [c[k]];
}
})
return a;
}, {});
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Related

How to filter array of array objects?

Trying to filter array of array objects. when variable matches with array of object value of dropDownOne key 'filterValue', then it will return dropDownTwo array,
let testName = ‘ filterValue’
var nestedArray = [
[
{
dropDownOne: {
key: "filterValue",
value: "test1"
},
dropDownTwo: [
{
key: "retrieveArrKey1",
value: "test123"
},
{
key: "retrieveArrKey2",
value: "test345"
}
]
}
],
[
{
dropDownOne: {
key: "NoFilter",
value: "test2"
},
dropDownTwo: [
{
key: "dropDown2",
value: "test"
},
{
key: "dropDown3",
value: "test"
}
]
}
]
]
Output =
dropDownTwo:[
{
key: "retrieveArrKey1",
value: "test123"
},
{
key: "retrieveArrKey2",
value: "test345"
}
]
Tried with this
let filterObj = nestedArray.filter((arr => arr.filter(value => {
if (value[0].dropDownOne.key === 'filterValue') {
return arr[1];
}
}))
But did not get the correct result
We can use Array.flat() and Array.flatMap() combined with Array.filter() to do it
let result = nestedArray.flat().filter(e => e.dropDownOne.key === testName).flatMap(e => e.dropDownTwo)
console.log(result)
let testName = `filterValue`
var nestedArray = [
[
{
dropDownOne: {
key: "filterValue",
value: "test1"
},
dropDownTwo: [
{
key: "retrieveArrKey1",
value: "test123"
},
{
key: "retrieveArrKey2",
value: "test345"
}
]
}
],
[
{
dropDownOne: {
key: "NoFilter",
value: "test2"
},
dropDownTwo: [
{
key: "dropDown2",
value: "test"
},
{
key: "dropDown3",
value: "test"
}
]
}
]
]
let result = nestedArray.flat().filter(e => e.dropDownOne.key === testName).flatMap(e => e.dropDownTwo)
console.log(result)
You can do it in this way :
nestedArray.filter(arr => arr.find(item=> item.dropDownOne.key === 'filterValue'))[0][0].dropDownTwo
var nestedArray = [
[
{
dropDownOne: {
key: "filterValue",
value: "test1"
},
dropDownTwo: [
{
key: "retrieveArrKey1",
value: "test123"
},
{
key: "retrieveArrKey2",
value: "test345"
}
]
}
],
[
{
dropDownOne: {
key: "NoFilter",
value: "test2"
},
dropDownTwo: [
{
key: "dropDown2",
value: "test"
},
{
key: "dropDown3",
value: "test"
}
]
}
]
]
const result = nestedArray.filter(arr => arr.find(item=> item.dropDownOne.key === 'filterValue'))[0][0].dropDownTwo
console.log(result)
You could use both flat() to flatten the nestedArray, then use the find() method to find the object with a dropDownOne.key that matches the value of testName.
it should then return the dropDownTwo property of that object.
let result = nestedArray.flat().find(obj => obj.dropDownOne.key === testName);
let filterObj = result ? result.dropDownTwo : [];
console.log(filterObj);
a full running example:
let testName = 'filterValue';
var nestedArray = [
[{
dropDownOne: {
key: "filterValue",
value: "test1"
},
dropDownTwo: [{
key: "retrieveArrKey1",
value: "test123"
},
{
key: "retrieveArrKey2",
value: "test345"
}
]
}],
[{
dropDownOne: {
key: "NoFilter",
value: "test2"
},
dropDownTwo: [{
key: "dropDown2",
value: "test"
},
{
key: "dropDown3",
value: "test"
}
]
}]
]
let result = nestedArray.flat().find(obj => obj.dropDownOne.key === testName);
let filterObj = result ? result.dropDownTwo : [];
console.log(filterObj);
Don't like other solutions due to too many array iterations. You could make a single run using reduce function which is a grandfather of many JS Array functions. It looks a bit worse than flat-filter-flat-..., but will probably work way faster due to much lower array iterations.
const testName = 'filterValue';
const nestedArray = [
[{
dropDownOne: {key: 'filterValue', value: 'test1'},
dropDownTwo: [
{key: 'retrieveArrKey1', value: 'test123'},
{key: 'retrieveArrKey2', value: 'test345'},
],
}],
[{
dropDownOne: {key: 'NoFilter', value: 'test2'},
dropDownTwo: [
{key: 'dropDown2', value: 'test'},
{key: 'dropDown3', value: 'test'},
],
}],
];
const result = nestedArray.reduce((acc, nestedItem) => {
nestedItem.forEach(item => {
if (item.dropDownOne.key === testName) {
acc.push(item.dropDownTwo);
}
});
return acc;
}, []);
console.log(result);

Merge 2 objects in an array where the values are an array

I am trying to merge values in 2 objects from the same array. The objects in this case are similar and the values I want to merge are arrays(Set)
var array = [
{
name: "foo1",
value: ["val1","val2"]
},
{
name: "foo1",
value: ["val2", "val3"]
},
{
name: "foo2",
value: ["val4"]
},
{
name: "foo2",
value: ["val4","val5"]
},
];
Expected Output
[
{
name: "foo1",
value: ["val1","val2", "val3"]
},{
name: "foo2",
value: ["val4","val4", "val5"]
}
]
My Code
var output = [];
array.forEach(function(item) {
var existing = output.filter(function(v, i) {
return v.name == item.name;
});
if (existing.length) {
var existingIndex = output.indexOf(existing[0]);
let newValue = new Set(output[existingIndex].value).add(item.value)
output[existingIndex].value = Array.from(newValue);
} else {
output.push(item);
}
});
Output Gotten
[ {
name: "foo1",
value: ["val1", "val2", ["val2", "val3"]]
}, {
name: "foo2",
value: ["val4", ["val4", "val5"]]
}]
How can I get the expected output (ES6 would also be preferred)
Try this
const array = [
{
"name": "foo1",
"value": [
"val1",
"val2",
"val3"
]
},
{
"name": "foo1",
"value": [
"val2",
"val3"
]
},
{
"name": "foo2",
"value": [
"val4",
"val5"
]
},
{
"name": "foo2",
"value": [
"val4",
"val5"
]
}
]
const result = []
for (const item of array) {
const existingItem = result.find(i => i.name === item.name)
if (existingItem) {
existingItem.value = [...new Set([...existingItem.value, ...item.value])]
} else {
result.push(item)
}
}
console.log(result)
Is this code solve your problem?
var array = [
{
name: "foo1",
value: ["val1","val2"]
},
{
name: "foo1",
value: ["val2", "val3"]
},
{
name: "foo2",
value: ["val4"]
},
{
name: "foo2",
value: ["val4","val5"]
},
];
var output = [];
array.forEach(function(item) {
var existing = output.filter(function(v, i) {
return v.name == item.name;
});
if (existing.length) {
var existingIndex = output.indexOf(existing[0]);
let newValue = new Set(output[existingIndex].value.concat(item.value))
output[existingIndex].value = Array.from(newValue);
} else {
output.push(item);
}
});
try
var arr = [
{
name: "foo1",
value: ["val1","val2"]
},
{
name: "foo1",
value: ["val2", "val3"]
},
{
name: "foo2",
value: ["val4"]
},
{
name: "foo2",
value: ["val4","val5"]
},
];
var arr2={}
arr.map((elem,ind)=>{
if(!arr2[elem.name]){
arr2[elem.name]=[]
}
arr2[elem.name]=[...arr2[elem.name],...elem.value]
})
arr=Object.keys(arr2);
arr.map((elem,ind)=>{
arr[ind]={name:elem,value:arr2[elem]};
})
You can do the following using reduce,
var array = [
{
name: "foo1",
value: ["val1","val2"]
},
{
name: "foo1",
value: ["val2", "val3"]
},
{
name: "foo2",
value: ["val4"]
},
{
name: "foo2",
value: ["val4","val5"]
},
];
res = array.reduce((prev, curr) => {
let index = prev.findIndex(item => item.name === curr.name);
if(index > -1) {
s = new Set([...prev[index].value, ...curr.value]);
prev[index].value = Array.from(s);
} else {
prev.push(curr);
}
return prev;
},[]);
console.log(res);
You could use reduce method with a Map as accumulator value and then use spread syntax ... on Map values to get an array of values.
var array = [{"name":"foo1","value":["val1","val2","val2","val3"]},{"name":"foo1","value":["val2","val3"]},{"name":"foo2","value":["val4","val4","val5"]},{"name":"foo2","value":["val4","val5"]}]
const map = array.reduce((r, { name, value }) => {
if (!r.has(name)) r.set(name, { name, value })
else r.get(name).value.push(...value)
r.get(name).value = [...new Set(r.get(name).value)]
return r;
}, new Map)
const result = [...map.values()]
console.log(result)
One approach is to create an unique list of keys and iterate over it. Create an array for each key and merge the values. The vanilla js way is:
Array.from(new Set(array.map(el => el.name)))
.map(name => ({
name,
value: Array.from(new Set(array.filter(el => el.name === name).flatMap(el => el.value)))
}))
Example:
const array = [
{
name: "foo1",
value: ["val1","val2"]
},
{
name: "foo1",
value: ["val2", "val3"]
},
{
name: "foo2",
value: ["val4"]
},
{
name: "foo2",
value: ["val4","val5"]
},
];
console.log(Array.from(new Set(array.map(el => el.name)))
.map(name => ({
name,
value: Array.from(new Set(array.filter(el => el.name === name).flatMap(el => el.value)))
})));
Using lodash you can reduce it to
_.uniq(array.map(el => el.name))
.map(name => ({
name,
value: _.uniq(array.filter(el => el.name === name).flatMap(el => el.value))
}))
Example:
const array = [
{
name: "foo1",
value: ["val1","val2"]
},
{
name: "foo1",
value: ["val2", "val3"]
},
{
name: "foo2",
value: ["val4"]
},
{
name: "foo2",
value: ["val4","val5"]
},
];
console.log(_.uniq(array.map(el => el.name))
.map(name => ({
name,
value: _.uniq(array.filter(el => el.name === name).flatMap(el => el.value))
})));
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.20/lodash.min.js"></script>
Find unique values of keys. Match this keys within array and return unique objects. Push this objects in an empty array. Then match other objects value with the new arrays objects value and push the unmatched values to this new array.
var arr = [
{
name: "foo1",
value: ["val1","val2"]
},
{
name: "foo1",
value: ["val2", "val3"]
},
{
name: "foo2",
value: ["val4"]
},
{
name: "foo2",
value: ["val4","val5"]
},
];
let key = [];
arr.map((val)=>key.push(val.name));
let uniquekeys = [...new Set(key)]; //unique values of keys
let newarr = [];
uniquekeys.map((uniquekey,ind)=>{
let reduceunique = arr.filter((vals)=>uniquekey == vals.name); // return matching objects as array
newarr.push(reduceunique[0]); // Push unique objects in an empty array
for(let i = 1; i<uniquekeys.length;i++){
reduceunique[i].value.map((val)=>{
let existvalue = newarr[ind].value.indexOf(val); // Match every value with the unique objects values
if(existvalue<0){
newarr[ind].value.push(val); // push the unmatched value in the array
}
});
};
});
console.log(newarr);
try to use Array.reduce and Array.filter to get the result like the following
var array = [
{
name: "foo1",
value: ["val1","val2"]
},
{
name: "foo1",
value: ["val2", "val3"]
},
{
name: "foo2",
value: ["val4"]
},
{
name: "foo2",
value: ["val4","val5"]
},
];
res = array.reduce((prev, curr) => {
let index = prev.findIndex(item => item.name === curr.name);
if(index > -1) {
prev[index].value = [...prev[index].value, ...curr.value];
prev[index].value = prev[index].value.filter((v,i) => prev[index].value.indexOf(v) === i)
} else {
prev.push(curr);
}
return prev;
},[]);
console.log(res);

How to get the value of each node of the tree structure

An array of nested arrays and objects, each node has a unique value, finding a value on this data, how to get the value on each node?
const opts = [
{
value: '01',
children: [
{ value: '0198' },
{ value: '0195', children: [{ value: '09977' }] }
]
},
{
value: '02',
children: [
{ value: '01986' },
{
value: '0195',
children: [
{ value: '09978', children: [{ value: '09864' }, { value: '90876' }] }
]
}
]
}
];
const code = '90876';
// expected get an array ['02','0195','09978','90876']
U could use a function to walk over the object structure recursively like described here:
const opts = [
{
value: '01',
children: [
{ value: '0198' },
{ value: '0195', children: [{ value: '09977' }] }
]
},
{
value: '02',
children: [
{ value: '01986' },
{
value: '0195',
children: [
{ value: '09978', children: [{ value: '09864' }, { value: '90876' }] }
]
}
]
}
];
function eachRecursive(obj, cb) {
for (var k in obj) {
if (typeof obj[k] == "object" && obj[k] !== null)
eachRecursive(obj[k], cb);
else
cb(obj[k]);
}
}
let results = [];
eachRecursive(opts, val => results.push(val));
console.log(results);
but not sure what you mean with your comment: // expected get an array ['02','0195','0997','90876'] can your explain why you expect that?
you can use a dfs algo
function dfs(o, target){
if(o.value == target) return [target];
if(!o.children) return false;
let path;
o.children.find(x=>path=dfs(x, target));
if(path){
return [o.value].concat(path);
}
};
const opts = [
{
value: '01',
children: [
{ value: '0198' },
{ value: '0195', children: [{ value: '09977' }] }
]
},
{
value: '02',
children: [
{ value: '01986' },
{
value: '0195',
children: [
{ value: '09978', children: [{ value: '09864' }, { value: '90876' }] }
]
}
]
}
];
let path;
opts.find(x=>path=dfs(x, '90876'))
console.log(path);
const opts = [
{
value: '01',
children: [
{ value: '0198' },
{ value: '0195', children: [{ value: '09977' }] }
]
},
{
value: '02',
children: [
{ value: '01986' },
{
value: '0195',
children: [
{ value: '09978', children: [{ value: '09864' }, { value: '90876' }] }
]
}
]
}
];
console.log(opts[1].value)
console.log(opts[1].children[1].value)
console.log(opts[1].children[1].children[0].value)
console.log(opts[1].children[1].children[0].children[1].value)

Search from an array of objects via array of string to get array of objects

Hie All,
I have two arrays as below:
var arr1 = [ '1956888670', '2109171907', '298845084' ];
var arr2 = [
{ KEY: '1262875245', VALUE: 'Vijay Kumar Verma' },
{ KEY: '1956888670', VALUE: 'Sivakesava Nallam' },
{ KEY: '2109171907', VALUE: 'udm analyst' },
{ KEY: '298845084', VALUE: 'Mukesh Nagora' },
{ KEY: '2007285563', VALUE: 'Yang Liu' },
{ KEY: '1976156380', VALUE: 'Imtiaz Zafar' },
];
arr1 has keys and arr2 has key and value.
i want to output key and value of only those keys which are present in arr1.
Hence my output should be,
[{ KEY: '1956888670', VALUE: 'Sivakesava Nallam' },
{ KEY: '2109171907', VALUE: 'udm analyst' },
{ KEY: '298845084', VALUE: 'Mukesh Nagora' },]
Requesting your help to write a function to fetch required output using javascript.Thanks in advance.
var arr1 = [ '1956888670', '2109171907', '298845084' ];
var arr2 = [
{ KEY: '1262875245', VALUE: 'Vijay Kumar Verma' },
{ KEY: '1956888670', VALUE: 'Sivakesava Nallam' },
{ KEY: '2109171907', VALUE: 'udm analyst' },
{ KEY: '298845084', VALUE: 'Mukesh Nagora' },
{ KEY: '2007285563', VALUE: 'Yang Liu' },
{ KEY: '1976156380', VALUE: 'Imtiaz Zafar' },
];
result = arr2.filter( function(obj) {
return arr1.indexOf(obj.KEY) >= 0;
});
console.info(result);
You can try with filter - Array.prototype.filter():
var output = arr2.filter(function(item) {
return arr1.indexOf(item.KEY) >= 0;
});
You could use Set for the filtering.
var arr1 = ['1956888670', '2109171907', '298845084'],
arr2 = [{ KEY: '1262875245', VALUE: 'Vijay Kumar Verma' }, { KEY: '1956888670', VALUE: 'Sivakesava Nallam' }, { KEY: '2109171907', VALUE: 'udm nalyst' }, { KEY: '298845084', VALUE: 'Mukesh Nagora' }, { KEY: '2007285563', ALUE: 'Yang Liu' }, { KEY: '1976156380', VALUE: 'Imtiaz Zafar' }],
mySet = new Set,
result;
arr1.forEach(function (a) {
mySet.add(a);
});
result = arr2.filter(function (a) {
return mySet.has(a.KEY);
});
console.log(result);

Recursively filter array of objects

Hitting a wall with this one, thought I would post it here in case some kind soul has come across a similar one. I have some data that looks something like this:
const input = [
{
value: 'Miss1',
children: [
{ value: 'Miss2' },
{ value: 'Hit1', children: [ { value: 'Miss3' } ] }
]
},
{
value: 'Miss4',
children: [
{ value: 'Miss5' },
{ value: 'Miss6', children: [ { value: 'Hit2' } ] }
]
},
{
value: 'Miss7',
children: [
{ value: 'Miss8' },
{ value: 'Miss9', children: [ { value: 'Miss10' } ] }
]
},
{
value: 'Hit3',
children: [
{ value: 'Miss11' },
{ value: 'Miss12', children: [ { value: 'Miss13' } ] }
]
},
{
value: 'Miss14',
children: [
{ value: 'Hit4' },
{ value: 'Miss15', children: [ { value: 'Miss16' } ] }
]
},
];
I don't know at run time how deep the hierarchy will be, i.e. how many levels of objects will have a children array. I have simplified the example somewhat, I will actually need to match the value properties against an array of search terms. Let's for the moment assume that I am matching where value.includes('Hit').
I need a function that returns a new array, such that:
Every non-matching object with no children, or no matches in children hierarchy, should not exist in output object
Every object with a descendant that contains a matching object, should remain
All descendants of matching objects should remain
I am considering a 'matching object' to be one with a value property that contains the string Hit in this case, and vice versa.
The output should look something like the following:
const expected = [
{
value: 'Miss1',
children: [
{ value: 'Hit1', children: [ { value: 'Miss3' } ] }
]
},
{
value: 'Miss4',
children: [
{ value: 'Miss6', children: [ { value: 'Hit2' } ] }
]
},
{
value: 'Hit3',
children: [
{ value: 'Miss11' },
{ value: 'Miss12', children: [ { value: 'Miss13' } ] }
]
},
{
value: 'Miss14',
children: [
{ value: 'Hit4' },
]
}
];
Many thanks to anyone who took the time to read this far, will post my solution if I get there first.
Using .filter() and making a recursive call as I described in the comment above is basically what you need. You just need to update each .children property with the result of the recursive call before returning.
The return value is just the .length of the resulting .children collection, so if there's at least one, the object is kept.
var res = input.filter(function f(o) {
if (o.value.includes("Hit")) return true
if (o.children) {
return (o.children = o.children.filter(f)).length
}
})
const input = [
{
value: 'Miss1',
children: [
{ value: 'Miss2' },
{ value: 'Hit1', children: [ { value: 'Miss3' } ] }
]
},
{
value: 'Miss4',
children: [
{ value: 'Miss5' },
{ value: 'Miss6', children: [ { value: 'Hit2' } ] }
]
},
{
value: 'Miss7',
children: [
{ value: 'Miss8' },
{ value: 'Miss9', children: [ { value: 'Miss10' } ] }
]
},
{
value: 'Hit3',
children: [
{ value: 'Miss11' },
{ value: 'Miss12', children: [ { value: 'Miss13' } ] }
]
},
{
value: 'Miss14',
children: [
{ value: 'Hit4' },
{ value: 'Miss15', children: [ { value: 'Miss16' } ] }
]
},
];
var res = input.filter(function f(o) {
if (o.value.includes("Hit")) return true
if (o.children) {
return (o.children = o.children.filter(f)).length
}
})
console.log(JSON.stringify(res, null, 2))
Note that .includes() on a String is ES7, so may need to be patched for legacy browsers. You can use the traditional .indexOf("Hit") != -1 in its place.
To not mutate the original, create a map function that copies an object and use that before the filter.
function copy(o) {
return Object.assign({}, o)
}
var res = input.map(copy).filter(function f(o) {
if (o.value.includes("Hit")) return true
if (o.children) {
return (o.children = o.children.map(copy).filter(f)).length
}
})
To really squeeze the code down, you could do this:
var res = input.filter(function f(o) {
return o.value.includes("Hit") ||
o.children && (o.children = o.children.filter(f)).length
})
Though it gets a little hard to read.
Here's a function that'll do what you're looking for. Essentially it will test every item in arr for a match, then recursively call filter on its children. Also Object.assign is used so that the underlying object isn't changed.
function filter(arr, term) {
var matches = [];
if (!Array.isArray(arr)) return matches;
arr.forEach(function(i) {
if (i.value.includes(term)) {
matches.push(i);
} else {
let childResults = filter(i.children, term);
if (childResults.length)
matches.push(Object.assign({}, i, { children: childResults }));
}
})
return matches;
}
I think it will be a recursive solution. Here is one that I tried.
function find(obj, key) {
if (obj.value && obj.value.indexOf(key) > -1){
return true;
}
if (obj.children && obj.children.length > 0){
return obj.children.reduce(function(obj1, obj2){
return find(obj1, key) || find(obj2, key);
}, {});
}
return false;
}
var output = input.filter(function(obj){
return find(obj, 'Hit');
});
console.log('Result', output);
const input = [
{
value: 'Miss1',
children: [
{ value: 'Miss1' },
{ value: 'Hit1', children: [ { value: 'Miss3' } ] }
]
},
{
value: 'Miss4',
children: [
{ value: 'Miss5' },
{ value: 'Miss6', children: [ { value: 'Hit2' } ] }
]
},
{
value: 'Miss7',
children: [
{ value: 'Miss8' },
{ value: 'Miss9', children: [ { value: 'Miss10' } ] }
]
},
{
value: 'Hit3',
children: [
{ value: 'Miss11' },
{ value: 'Miss12', children: [ { value: 'Miss13' } ] }
]
},
{
value: 'Miss14asds',
children: [
{ value: 'Hit4sdas' },
{ value: 'Miss15', children: [ { value: 'Miss16' } ] }
]
},
];
function filter(arr, term) {
var matches = [];
if (!Array.isArray(arr)) return matches;
arr.forEach(function(i) {
if (i.value === term) {
const filterData = (i.children && Array.isArray(i.children))? i.children.filter(values => values.value ===term):[];
console.log(filterData)
i.children =filterData;
matches.push(i);
} else {
// console.log(i.children)
let childResults = filter(i.children, term);
if (childResults.length)
matches.push(Object.assign({}, i, { children: childResults }));
}
})
return matches;
}
const filterData= filter(input,'Miss1');
console.log(filterData)
Below code for filter the parent and child array data using recursive function
const input = [
{
value: 'Miss1',
children: [
{ value: 'Miss2' },
{ value: 'Hit1', children: [ { value: 'Miss3' } ] }
]
},
{
value: 'Miss4',
children: [
{ value: 'Miss5' },
{ value: 'Miss6', children: [ { value: 'Hit2' } ] }
]
},
{
value: 'Miss7',
children: [
{ value: 'Miss8' },
{ value: 'Miss9', children: [ { value: 'Miss10' } ] }
]
},
{
value: 'Hit3',
children: [
{ value: 'Miss11' },
{ value: 'Miss12', children: [ { value: 'Miss13' } ] }
]
},
{
value: 'Miss14',
children: [
{ value: 'Hit4' },
{ value: 'Miss15', children: [ { value: 'Miss16' } ] }
]
},
];
var res = input.filter(function f(o) {
if (o.value.includes("Hit")) return true
if (o.children) {
return (o.children = o.children.filter(f)).length
}
})
console.log(JSON.stringify(res, null, 2))
Here is a good solution which utilizes the array reduce function which results in more readable code then the other solutions. Also it is more readable in my opinion. We are calling the filter function recursively to filter an array along with its children
const input = [
{
value: "Miss1",
children: [
{ value: "Miss2" },
{ value: "Hit1", children: [{ value: "Miss3" }] },
],
},
{
value: "Miss4",
children: [
{ value: "Miss5" },
{ value: "Miss6", children: [{ value: "Hit2" }] },
],
},
{
value: "Miss7",
children: [
{ value: "Miss8" },
{ value: "Miss9", children: [{ value: "Miss10" }] },
],
},
{
value: "Hit3",
children: [
{ value: "Miss11" },
{ value: "Miss12", children: [{ value: "Miss13" }] },
],
},
{
value: "Miss14",
children: [
{ value: "Hit4" },
{ value: "Miss15", children: [{ value: "Miss16" }] },
],
},
];
function recursiveFilter(arr) {
return arr.reduce(function filter(prev, item) {
if (item.value.includes("Hit")) {
if (item.children && item.children.length > 0) {
return prev.concat({
...item,
children: item.children.reduce(filter, []),
});
} else {
return item;
}
}
return prev;
}, []);
}
console.log(recursiveFilter(input));
Array.prototype.flatMap is a good fit for recursive filtering. Similar to map, filter and reduce, using flatMap does not modify the original input -
const findHits = (t = []) =>
t.flatMap(({ value, children }) => {
if (value.startsWith("Hit"))
return [{ value, children }]
else {
const r = findHits(children)
return r.length ? [{ value, children: r }] : []
}
})
const input =
[{value:'Miss1',children:[{value:'Miss2'},{value:'Hit1', children:[{value:'Miss3'}]}]},{value:'Miss4',children:[{value:'Miss5'},{value:'Miss6', children:[{value:'Hit2'}]}]},{value:'Miss7',children:[{value:'Miss8'},{value:'Miss9', children:[{value:'Miss10'}]}]},{value:'Hit3',children:[{value:'Miss11'},{value:'Miss12', children:[{value:'Miss13'}]}]},{value:'Miss14',children:[{value:'Hit4'},{value:'Miss15', children:[{value:'Miss16'}]}]}]
const result =
findHits(input)
console.log(JSON.stringify(result, null, 2))
[
{
"value": "Miss1",
"children": [
{
"value": "Hit1",
"children": [
{
"value": "Miss3"
}
]
}
]
},
{
"value": "Miss4",
"children": [
{
"value": "Miss6",
"children": [
{
"value": "Hit2"
}
]
}
]
},
{
"value": "Hit3",
"children": [
{
"value": "Miss11"
},
{
"value": "Miss12",
"children": [
{
"value": "Miss13"
}
]
}
]
},
{
"value": "Miss14",
"children": [
{
"value": "Hit4"
}
]
}
]
Alternatively you can use _.filterDeep method from deepdash extension for lodash:
var keyword = 'Hit';
var foundHit = _.filterDeep(
input,
function(value) {
return value.value.includes(keyword);
},
{
tree: true,
onTrue: { skipChildren: true },
}
);
Here is a full test for your case
Here is a different type of solution using object-scan.
This solution is iterative instead of recursive. It works because object-scan iterates in delete safe order. Basically we traverse into the tree and break on any match. Then we keep track of matches at the different depth (ensuring that we reset that information appropriately as we traverse into a neighbouring branches).
It's mostly an academic exercise as the recursive approach is cleaner and faster. However this answer might be interesting if there is additional processing that needs to be done or stack depth errors are a problem.
// const objectScan = require('object-scan');
const myInput = [{ value: 'Miss1', children: [{ value: 'Miss2' }, { value: 'Hit1', children: [{ value: 'Miss3' }] }] }, { value: 'Miss4', children: [{ value: 'Miss5' }, { value: 'Miss6', children: [{ value: 'Hit2' }] }] }, { value: 'Miss7', children: [{ value: 'Miss8' }, { value: 'Miss9', children: [{ value: 'Miss10' }] }] }, { value: 'Hit3', children: [{ value: 'Miss11' }, { value: 'Miss12', children: [{ value: 'Miss13' }] }] }, { value: 'Miss14', children: [{ value: 'Hit4' }, { value: 'Miss15', children: [{ value: 'Miss16' }] }] }];
const myFilterFn = ({ value }) => value.includes('Hit');
const rewrite = (input, filterFn) => objectScan(['**(^children$)'], {
breakFn: ({ isMatch, value }) => isMatch && filterFn(value),
filterFn: ({
parent, property, key, value, context
}) => {
const len = (key.length - 1) / 2;
if (context.prevLen <= len) {
context.matches.length = context.prevLen + 1;
}
context.prevLen = len;
if (context.matches[len + 1] === true || filterFn(value)) {
context.matches[len] = true;
return false;
}
parent.splice(property, 1);
return true;
},
useArraySelector: false,
rtn: 'count'
})(input, { prevLen: 0, matches: [] });
console.log(rewrite(myInput, myFilterFn)); // returns number of deletions
// => 8
console.log(myInput);
// => [ { value: 'Miss1', children: [ { value: 'Hit1', children: [ { value: 'Miss3' } ] } ] }, { value: 'Miss4', children: [ { value: 'Miss6', children: [ { value: 'Hit2' } ] } ] }, { value: 'Hit3', children: [ { value: 'Miss11' }, { value: 'Miss12', children: [ { value: 'Miss13' } ] } ] }, { value: 'Miss14', children: [ { value: 'Hit4' } ] } ]
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#16.0.0"></script>
Disclaimer: I'm the author of object-scan
Was looking for another way to solve this problem without directly mutating the children array and came up with this:
const input = [
{
value: 'Miss1',
children: [
{ value: 'Miss2' },
{ value: 'Hit1', children: [ { value: 'Miss3' } ] }
]
},
{
value: 'Miss4',
children: [
{ value: 'Miss5' },
{ value: 'Miss6', children: [ { value: 'Hit2' } ] }
]
},
{
value: 'Miss7',
children: [
{ value: 'Miss8' },
{ value: 'Miss9', children: [ { value: 'Miss10' } ] }
]
},
{
value: 'Hit3',
children: [
{ value: 'Miss11' },
{ value: 'Miss12', children: [ { value: 'Miss13' } ] }
]
},
{
value: 'Miss14',
children: [
{ value: 'Hit4' },
{ value: 'Miss15', children: [ { value: 'Miss16' } ] }
]
},
];
const filtered = input.reduce(function fr(acc, curr) {
if (curr.children) {
const children = curr.children.reduce(fr, []);
if (children.length) {
return acc.concat({ ...curr, children: children });
}
}
if (curr.value.includes('Hit')) {
return acc.concat(curr);
} else {
return acc;
}
}, []);
console.log(filtered);

Categories