【问题标题】:Filter an object by partial paths按部分路径过滤对象
【发布时间】:2019-09-01 15:39:27
【问题描述】:

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

举个例子。

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

path1:countr.cit

对于addresspath1 将导致

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


路径2:国家

对于路径 2, 我应该得到整个 address 对象,因为 countr 存在于 countrynearbyCountry

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

编辑:当给出一个确切的路径时,我已经能够解决这个问题(例如:country.city)。但是部分路径有困难。

【问题讨论】:

  • 您可以添加完整路径的解决方案吗?
  • 它实际上非常简单。检查this

标签: javascript


【解决方案1】:

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

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

【讨论】:

  • 谢谢。 ([key]) => key.toLowerCase().includes(part) 可以是 ([key]) => key.toLowerCase().includes(part.toLowerCase())
【解决方案2】:
  • 您可以创建一个递归函数,它将一个对象和一个部分路径数组作为参数。
  • 获取最新的部分路径和filterinclude该路径的键
  • 如果没有键,则返回 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)

【讨论】:

  • 这种方法的问题是您一次只能在输出中拥有一个子树。例如:对于路径“国家”,它不会给出nearbyCountry:'Korea' 子树。
  • OP 说:“对于 path2,我应该获取整个地址对象,因为国家 存在于国家和附近国家startsWith 将不起作用既不是includes()。您需要一个不区分大小写的RegExp
【解决方案3】:

由于我不完全理解您的目标(例如:为什么在您的第一个输出示例中保留 country 对象的 name 属性而不是 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" 过滤时,即使内部没有与cit 匹配的内部key,此方法也会保留key = nearbyCountry

第二种方法

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

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 提供的答案中。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-05-04
    • 2015-08-04
    • 1970-01-01
    • 2021-01-07
    • 1970-01-01
    • 2022-01-18
    • 2018-04-03
    • 2022-06-10
    相关资源
    最近更新 更多