通过部分路径过滤对象

时间:2019-04-11 05:06:58

标签: javascript

如何根据给定的部分路径过滤对象?

作为一个例子。

let address  = {
  country :{
    name:'Japan',
    city:{
      name:'Tokyo',
      town: {
        name:'korushawa'
      }
    },
    code:'JP'
  },
  nearbyCountry:'Korea'
}

路径1: countr.cit

对于address路径1将导致

{
  country :{
    city:{
      name:'Tokyo',
      town: {
        name:'korushawa'
      }
    }
  }
}


路径2:计数器

对于路径2, 我应该获取整个address对象,因为 counter 存在于countrynearbyCountry

{
  country :{
    name:'Japan',
    city:{
      name:'Tokyo',
      town: {
        name:'korushawa'
      }
    }
  },
  nearbyCountry:'Korea'
}

编辑:给定确切的路径(例如:country.city),我已经能够解决此问题。但是在局部路径上有困难。

3 个答案:

答案 0 :(得分:4)

  • 您可以创建一个将对象和部分路径数组作为参数的递归函数。
  • 获取最新的部分路径,并filter使用include该路径的键
  • 如果没有键,则返回null
  • 否则,使用reduce从键创建对象
    • 如果没有其他键,只需将obj的所有过滤后的属性添加到累加器
    • 否则,以递归方式调用该函数以获取另一个嵌套对象

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;}

如果只需要第一个与密钥完全或部分匹配的密钥,则可以拆分path并像这样使用reduce。如果找到了键,则返回对象,否则找到include给定键的键(这提供了最后一个键匹配的数据。不是整个对象树)

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)

答案 1 :(得分:4)

由于我不完全了解您的目标(例如:为什么在第一个输出示例中,您保留name对象的country属性而不是code属性),我会给您两种方法,希望对您有帮助。

第一种方法:

在第一种方法中,我们递归遍历主要对象以过滤出在特定级别不匹配的keys。输出将是object,其属性在特定级别上匹配:

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;}

如您所见,当通过"countr.cit"进行过滤时,即使内部key = nearbyCountrykey不匹配,这种方法也会保留cit。 / p>

第二种方法

在这种方法中,我们将过滤掉主first-level keys中所有object的所有部分都不匹配的path。但是,我不得不说这种方法有点奇怪。我相信,如果您的输入是一个对象数组,而不仅仅是一个对象,那么这样会更有意义。

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;}

最后, @adiga 提供的答案中已经显示了您可能想要做的另一件事。

答案 2 :(得分:4)

这种方法依赖于过滤条目并通过映射具有单个属性的对象来重建新对象。

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; }