如何创建一个新对象作为任意深度对象属性的子集,同时动态重命名属性
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
}
虽然有大量帖子专门讨论该主题,但我仍然找不到令人满意的想法如何对 任意 深度的对象属性进行子集化。更重要的是,我还想即时重命名 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 帖子,我发现
你可以这样做
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
}