How to filter an object given its partial path?
As an example.
let address = {
country :{
name:'Japan',
city:{
name:'Tokyo',
town: {
name:'korushawa'
}
},
code:'JP'
},
nearbyCountry:'Korea'
}
path1: countr.cit
For address
, path1 will result in
{
country :{
city:{
name:'Tokyo',
town: {
name:'korushawa'
}
}
}
}
path2: countr
For path2,
I should get entire address
object because countr is present in country
and nearbyCountry
{
country :{
name:'Japan',
city:{
name:'Tokyo',
town: {
name:'korushawa'
}
}
},
nearbyCountry:'Korea'
}
Edit: I have been able to solve this when given an exact path (ex: country.city
). But having difficulty with partial paths.
How to filter an object given its partial path?
As an example.
let address = {
country :{
name:'Japan',
city:{
name:'Tokyo',
town: {
name:'korushawa'
}
},
code:'JP'
},
nearbyCountry:'Korea'
}
path1: countr.cit
For address
, path1 will result in
{
country :{
city:{
name:'Tokyo',
town: {
name:'korushawa'
}
}
}
}
path2: countr
For path2,
I should get entire address
object because countr is present in country
and nearbyCountry
{
country :{
name:'Japan',
city:{
name:'Tokyo',
town: {
name:'korushawa'
}
}
},
nearbyCountry:'Korea'
}
Edit: I have been able to solve this when given an exact path (ex: country.city
). But having difficulty with partial paths.
- Can you add the solution you have for full paths? – Steven B. Commented Apr 11, 2019 at 5:09
- Its actually very straightforward. Check this – asdasd Commented Apr 11, 2019 at 5:10
3 Answers
Reset to default 4 +50This approach rely on filtering the entries and rebuilding a new object by mapping objects with a single property.
function getParts(object, fragments) {
var [part, ...rest] = fragments.split('.');
return Object.assign({}, ...Object
.entries(object)
.filter(([key]) => key.toLowerCase().includes(part))
.map(([k, v]) => {
if (!rest.length) return { [k]: v };
var parts = v && typeof v === 'object' && getParts(v, rest.join('.'));
if (parts) return { [k]: parts };
})
);
}
let address = { country: { name: 'Japan', city: { name: 'Tokyo', town: { name: 'korushawa' } }, code: 'JP' }, nearbyCountry: 'Korea' };
console.log(getParts(address, 'countr.cit'));
console.log(getParts(address, 'countr'));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Since I don't fully understand your goal (for example: why on your first output example do you keep name
property of country
object but not the code
property), I will give you two approachs that I hope can help you.
First approach:
In this first approach we recursively traverse the main object to filter out the keys
that don't match at a particular level. The output will be an object
with the properties that match at a specific level:
let address = {
country: {
name: 'Japan',
city: {name: 'Tokyo', town: {name: 'korushawa'}},
code: 'JP'
},
nearbyCountry: {name: 'Korea', code: 'KO'}
};
const myFilter = (obj, keys) =>
{
if (keys.length <= 0)
return obj;
return Object.keys(obj).reduce(
(nObj, k) => k.toLowerCase().match(keys[0])
? ({...nObj, [k]: myFilter(obj[k], keys.slice(1))})
: nObj,
{}
);
}
const customFilter = (o, k) => myFilter(
JSON.parse(JSON.stringify(o)),
k.split(".").map(x => x.toLowerCase())
);
console.log("Filter by 'countr':", customFilter(address, "countr"));
console.log("Filter by 'countr.cit':", customFilter(address, "countr.cit"));
console.log("Filter by 'countr.cit.to':", customFilter(address, "countr.cit.to"));
console.log("Filter by 'countr.cit.lala':", customFilter(address, "countr.cit.lala"));
.as-console {background-color:black !important; color:lime;}
.as-console-wrapper {max-height:100% !important; top:0;}
As you can see, when filtering by "countr.cit"
this approach keeps the key = nearbyCountry
even if there isn't an inner key
matching with cit
inside of it.
Second approach
In this approach we going to filter out all the first-level keys
of the main object
that don't have a match on all the sections of the provided path
. However, I have to say that this approach is a little strange. I believe this will have more sense if your input is an array of objects, not just one object.
let address = {
country: {
name: 'Japan',
city: {name: 'Tokyo', town: {name: 'korushawa'}},
code: 'JP'
},
nearbyCountry: {name: 'Korea', code: 'KO'}
};
const myFilter = (obj, paths) =>
{
let newObj = {};
Object.entries(obj).forEach(([key, val]) =>
{
let res = paths.slice(1).reduce((o, cp) =>
{
if (o === undefined) return o;
let found = Object.keys(o).find(k => k.toLowerCase().match(cp));
return found !== undefined ? o[found] : found;
}, val);
if (key.toLowerCase().match(paths[0]) && res !== undefined)
newObj[key] = val;
});
return newObj;
}
const customFilter = (o, k) => myFilter(
JSON.parse(JSON.stringify(o)),
k.split(".").map(x => x.toLowerCase())
);
console.log("Filter by 'countr':", customFilter(address, "countr"));
console.log("Filter by 'countr.cit':", customFilter(address, "countr.cit"));
console.log("Filter by 'countr.cit.to':", customFilter(address, "countr.cit.to"));
console.log("Filter by 'countr.cit.lala':", customFilter(address, "countr.cit.lala"));
.as-console {background-color:black !important; color:lime;}
.as-console-wrapper {max-height:100% !important; top:0;}
Finally, the other thing you may want to do is already shown on the answer provided by @adiga.
- You could create a recursive function which takes an object and an array of partial paths as a parameter.
- Get the latest partial path and
filter
the keys whichinclude
that path - If there are no keys, return null
- Else, create an object from the keys using
reduce
- if there are no more keys left, just add the all the filtered properties of
obj
to the accumulator - else, recursively call the function get another nested object
- if there are no more keys left, just add the all the filtered properties of
const address={country:{name:'Japan',city:{name:'Tokyo',town:{name:'korushawa'}},code:'JP'},nearbyCountry:'Korea'};
function filterObject(obj, paths) {
if (!obj) return null;
const partial = paths.shift(),
filteredKeys = Object.keys(obj).filter(k => k.toLowerCase().includes(partial));
if (!filteredKeys.length) return null; // no keys with the path found
return filteredKeys.reduce((acc, key) => {
if(!paths.length) return { ...acc, [key]: obj[key] }
const nest = filterObject(obj[key], [...paths]) // filter another level
return nest ? { ...acc, [key]: nest } : acc
}, null)
}
let path;
console.log(path = 'countr', ':');
console.log(filterObject(address, path.split('.')))
console.log(path = 'countr.cit', ':');
console.log(filterObject(address, path.split('.')))
console.log(path = 'countr.cit.to', ':');
console.log(filterObject(address, path.split('.')))
console.log(path = 'countr.cit.doesntexist', ':');
console.log(filterObject(address, path.split('.')))
.as-console-wrapper {max-height:100% !important; top:0;}
If you just need the first key that fully or partial matches the keys, you could split the path
and use reduce
like this. If the key is found, return the object, else find the keys which include
the given key (This gives the data for the last key match. Not the entire object tree)
const address={country:{name:'Japan',city:{name:'Tokyo',town:{name:'korushawa'}},code:'JP'},nearbyCountry:'Korea'},
path = "countr.cit";
const output = path.split('.').reduce((obj, key) => {
if (key in obj) {
return obj[key];
} else {
let found = Object.keys(obj).find(k => k.includes(key));
if (found)
return obj[found]
else
return {}
}
}, address);
console.log(output)