最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Filter an object by partial paths - Stack Overflow

programmeradmin0浏览0评论

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.

Share Improve this question edited Apr 20, 2019 at 12:55 asdasd asked Apr 11, 2019 at 5:06 asdasdasdasd 7,2506 gold badges15 silver badges36 bronze badges 2
  • 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
Add a ment  | 

3 Answers 3

Reset to default 4 +50

This 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 which include 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

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)

发布评论

评论列表(0)

  1. 暂无评论