'Filtering a nested object depending on a key array in Javascript
I have been provided with an object and an array, I need to filter out the object in such a way that it contains only the nodes where either the key or the value matches with the given keys in array. For e.g.,
Array containing keys - ['student1', 'STU', 'LMN', 'student4']
INPUT
{
student1: {
name: 'ABC',
roll: 17,
},
student2: {
name: 'LMN',
roll: 16
},
student3: {
name: 'MNO',
roll: 15
},
student4: {
name: 'PQR',
roll: 16
}
}
OUTPUT
{
student1: {
name: 'ABC',
roll: 17,
},
student2: {
name: 'LMN',
roll: 16
},
student4: {
name: 'PQR',
roll: 16
}
}
INPUT
{
student1: {
name: 'ABC',
roll: 17,
friends: {
student5: {},
student6: {}
}
},
student2: {
name: 'LMN',
roll: 16
},
student3: {
name: 'MNO',
roll: 15,
friends: {
student7: {
name: 'STU'
}
}
},
student4: {
name: 'PQR',
roll: 16
}
}
OUTPUT:
{
student1: {
name: 'ABC',
roll: 17,
friends: {
student5: {},
student6: {}
}
},
student2: {
name: 'LMN',
roll: 16
},
student3: {
name: 'MNO',
roll: 15,
friends: {
student7: {
name: 'STU'
}
}
},
student4: {
name: 'PQR',
roll: 16
}
}
INPUT
{
student5: {
name: 'EFG',
roll: 10,
friends: {
student1: {},
student2: {}
}
}
}
OUTPUT:
{
student5: {
name: 'EFG',
roll: 10,
friends: {
student1: {}
}
}
}
Explanation :
Keys are 'student1', 'student4', 'STU' and 'LMN', so we parse the entire object and wherever we get either the key or the value from the object matching those keys, we include that in resultant object else we discard it.
Also, if we get a key in the object matching the keys provided in the array, we do not need to check the inside of that particular entry. For e.g., in case of 'student1', since it's included in the keys array, we do not need to check what it contains, we can blindly include it in target object.
NOTE: The object can be deeply nested also.
MY APPROACH
I was trying to parse the entire object and filter out the entries depending on whether it's present or not, but it doesn't seem to give correct results,
const containsKey = (obj, arr = []) => {
return (
obj &&
typeof obj === "object" &&
(arr.some((key) => key in obj) ||
arr.indexOf(obj[targetKey]) > -1 ||
Object.values(obj).filter((obj) => {
return containsKey(obj, arr);
}))
);
};
Object.fromEntries(
Object.entries(obj).filter(([k, v]) =>
containsKey({ [k]: v }, arr)
)
);
Any sort of help will be highly appreciated. Thanks :)
Solution 1:[1]
This approach uses three functions:
toList: a recursive function that flattens any object into a one-dimensional array.inList: a function that returns a boolean to indicate whether or not one of the items in the resulting list is in the array of keyskeysfilObject: a function that "filters" the given object depending on the boolean returned
const keys = ['student1', 'STU', 'LMN', 'student4'],
input1 = { student1: { name: 'ABC', roll: 17, }, student2: { name: 'LMN', roll: 16 }, student3: { name: 'MNO', roll: 15 }, student4: { name: 'PQR', roll: 16 } },
input2 = { student1: { name: 'ABC', roll: 17, friends: { student5: {}, student6: {} } }, student2: { name: 'LMN', roll: 16 }, student3: { name: 'MNO', roll: 15, friends: { student7: { name: 'STU' } } }, student4: { name: 'PQR', roll: 16 } },
toList = o => Object.entries(o).flat().map(
v =>
typeof(v) === 'object' && !Array.isArray(v) ?
toList(v) :
v
)
.flat(),
inList = (list, o) => toList(o).some(k => list.includes(k)),
filObject = (list,obj) => Object.entries(obj).reduce(
(acc,[key,value]) =>
inList(list,{[key]:value}) ? {...acc,[key]:value} : acc, {}
);
console.log( filObject(keys,input1) );
console.log( filObject(keys,input2) );
Updated version ...
The function filObject in this version uses recursion to filter deeper parts where the key fails the test but the value passes. The line inList(list,{[key]:value}) ? {...acc,[key]:value} : acc, {} was replaced by list.includes(key) ? {...acc,[key]:value} : inList(list,value) ? {...acc,[key]:filObject(list,value)} : acc, {}.
const keys = ['student1', 'STU', 'LMN', 'student4'],
input1 = { student1: { name: 'ABC', roll: 17, }, student2: { name: 'LMN', roll: 16 }, student3: { name: 'MNO', roll: 15 }, student4: { name: 'PQR', roll: 16 } },
input2 = { student1: { name: 'ABC', roll: 17, friends: { student5: {}, student6: {} } }, student2: { name: 'LMN', roll: 16 }, student3: { name: 'MNO', roll: 15, friends: { student7: { name: 'STU' } } }, student4: { name: 'PQR', roll: 16 } },
input3 = { student5: { name: 'EFG', roll: 10, friends: { student1: {}, student2: {} } } },
toList = o => Object.entries(o).flat().map(
v =>
typeof(v) === 'object' && !Array.isArray(v) ?
toList(v) :
v
)
.flat(),
inList = (list, o) => toList(o).some(k => list.includes(k)),
filObject = (list,obj) => Object.entries(obj).reduce(
(acc,[key,value]) =>
[key,value].some(p => list.includes(p)) ? {...acc,[key]:value} :
inList(list,value) ? {...acc,[key]:filObject(list,value)} : acc, {}
);
console.log( filObject(keys,input1) );
console.log( filObject(keys,input2) );
console.log( filObject(keys,input3) );
Solution 2:[2]
You can simply iterate through Object.entries(object) and check if the key or value of entry exists in the keys array. If a value is another object repeat the same for it.
function checkNested(object, keys) {
for(const [key, value] of Object.entries(object)){
if(
(keys.includes(key) || keys.includes(value))
&&
(value != null && typeof value == 'object' && checkNested(value, keys))
) {
return true;
}
}
return false;
}
function filterWithKeys(object, keys) {
const result = {};
for(const [key, value] of Object.entries(object)){
if(
(keys.includes(key) || keys.includes(value))
&&
(value != null && typeof value == 'object' && checkNested(value, keys))
) {
result[key] = value;
}
}
return result;
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | Dipanshu Mahla |
