如何创建一个新对象作为任意深度对象属性的子集,同时动态重命名属性

How to create a new object as a subset of object properties of any depth, while renaming properties on the fly

虽然有大量帖子专门讨论该主题,但我仍然找不到令人满意的想法如何对 任意 深度的对象属性进行子集化。更重要的是,我还想即时重命名 selected 键。

我的目标是实现一个通用函数,我们称它为 select(),它接受两个输入:

例如,考虑以下数据:

const earthData = {
  distanceFromSun: 149280000,
  continents: {
    asia: {
      area: 44579000,
      population: 4560667108,
      countries: { japan: { temperature: 62.5 } },
    },
    africa: { area: 30370000, population: 1275920972 },
    europe: { area: 10180000, population: 746419440 },
    america: { area: 42549000, population: 964920000 },
    australia: { area: 7690000, population: 25925600 },
    antarctica: { area: 14200000, population: 5000 },
  },
};

我的目标是这样调用 select()

const earthDataSubset = select(earthData, {
  distanceFromSun: ['distanceFromSun'],
  asiaPop: ['continents', 'asia', 'population'],
  americaArea: ['continents', 'america', 'area'],
  japanTemp: ['continents', 'asia', 'countries', 'japan', 'temperature'],
});

结果 earthDataSubset

// earthDataSubset
{
    distanceFromSun: 149280000,
    asiaPop: 4560667108,
    americaArea: 42549000,
    japanTemp: 62.5
}

在这一点上,有人可能会问我为什么不简单地这样做:

const earthDataSubsetSimple = {
    distanceFromSun: earthData.distanceFromSun,
    asiaPop: earthData.continents.asia.population,
    americaArea: earthData.continents.america.area,
    japanTemp: earthData.continents.asia.countries.japan.temperature
}

这行不通,因为通常情况下,我的数据以 array 对象的形式到达,所以我需要 map 遍历数组并应用相同的 select 程序,例如:

const earthData = {
    distanceFromSun: 149280000,
    continents: {
      asia: {
        area: 44579000,
        population: 4560667108,
        countries: { japan: { temperature: 62.5 } },
      },
      africa: { area: 30370000, population: 1275920972 },
      europe: { area: 10180000, population: 746419440 },
      america: { area: 42549000, population: 964920000 },
      australia: { area: 7690000, population: 25925600 },
      antarctica: { area: 14200000, population: 5000 },
    },
  };

  const earthData2050 = {
    distanceFromSun: 149280000,
    continents: {
      asia: {
        area: 44579000,
        population: 4560767108,
        countries: { japan: { temperature: 73.6 } },
      },
      africa: { area: 30370000, population: 1275960972 },
      europe: { area: 10180000, population: 746419540 },
      america: { area: 42549000, population: 964910000 },
      australia: { area: 7690000, population: 25928600 },
      antarctica: { area: 14200000, population: 5013 },
    },
  };

const myEarthArr = [earthData, earthData2050]

诚然,我可以简单地调用 .map() 数组方法:

const mapRes = myEarthArr.map((record) => ({
  distanceFromSun: record.distanceFromSun,
  asiaPop: record.continents.asia.population,
  americaArea: record.continents.america.area,
  japanTemp: record.continents.asia.countries.japan.temperature,
}));

并获得所需的输出:

// [ { distanceFromSun: 149280000,
//     asiaPop: 4560667108,
//     americaArea: 42549000,
//     japanTemp: 62.5 },
//   { distanceFromSun: 149280000,
//     asiaPop: 4560767108,
//     americaArea: 42549000,
//     japanTemp: 73.6 } ]

尽管如此,我希望创建自己的 generic select() 函数,它接受一个对象并将其子集化.这种方法的好处是它的灵活性。我可以在单个对象上独立使用它,还允许我在需要时将 select() 扩展到对象数组,方法是:

// pseudo code
myEarthArr.map( (record) => select(record, {
  distanceFromSun: ['distanceFromSun'],
  asiaPop: ['continents', 'asia', 'population'],
  americaArea: ['continents', 'america', 'area'],
  japanTemp: ['continents', 'asia', 'countries', 'japan', 'temperature'],
}) )

通过查看 Whosebug 帖子,我发现 to be the closest. But neither do I understand how to shape it to my needs, nor whether its recursive mechanism actually required in my situation. By contrast, this post 为简单的子集化场景提供了大量解决方案,但 none 正在解决嵌套属性的问题。

你可以这样做

const select = (data, filters) => Object.entries(filters)
.reduce((res, [key, path]) => {
  return {
   ...res,
   [key]: path.reduce((current, segment) => current[segment] ?? undefined , data)
  }

}, {})
  

const earthData = {
  distanceFromSun: 149280000,
  continents: {
    asia: {
      area: 44579000,
      population: 4560667108,
      countries: { japan: { temperature: 62.5 } },
    },
    africa: { area: 30370000, population: 1275920972 },
    europe: { area: 10180000, population: 746419440 },
    america: { area: 42549000, population: 964920000 },
    australia: { area: 7690000, population: 25925600 },
    antarctica: { area: 14200000, population: 5000 },
  },
};


const earthDataSubset = select(earthData, {
  distanceFromSun: ['distanceFromSun'],
  asiaPop: ['continents', 'asia', 'population'],
  americaArea: ['continents', 'america', 'area'],
  japanTemp: ['continents', 'asia', 'countries', 'japan', 'temperature'],
});

console.log(earthDataSubset)

说明内部reduce部分

path.reduce((current, segment) => current[segment] ?? undefined , data)

path 是嵌套在 data

中的 属性 数组

path.reduce循环所有这些属性名字

例子

path = ['continents', 'asia', 'population']

在第一次迭代中

  • current是data your object(有点长就省略了)
  • 段是 'continents'
  • return数据['continents']

第二次迭代

  • 当前为数据['continents']
  • 段是'asia'
  • return数据['continents']['asia']

你明白了

以下是使用 JSONPath 的方法。

安装 jsonpath-plus npm 包:

npm install jsonpath-plus

然后你可以这样写你的函数(假设你使用的是模块):

import { JSONPath } from "jsonpath-plus";

function select(data, query) {
    let result = {};

    for (let key of Object.keys(query)) {
        result[key] = JSONPath(`$.${query[key].join('.')}`, data)[0];
    }

    return result;
}

使用您的数据(myEarthArr,为简洁起见,我在此省略)和查询对象:

const myQuery = {
    distanceFromSun: ['distanceFromSun'],
    asiaPop: ['continents', 'asia', 'population'],
    americaArea: ['continents', 'america', 'area'],
    japanTemp: ['continents', 'asia', 'countries', 'japan', 'temperature'],
};

运行 这个循环:

for (let dataItem of myEarthArr) {
    console.log(select(dataItem, myQuery));
}

产生:

{
  distanceFromSun: 149280000,
  asiaPop: 4560667108,
  americaArea: 42549000,
  japanTemp: 62.5
}
{
  distanceFromSun: 149280000,
  asiaPop: 4560767108,
  americaArea: 42549000,
  japanTemp: 73.6
}