Although a plethora of posts have been dedicated to the topic, I still couldn’t find a satisfying idea how to subset object properties of any depth. More so, I would also like to rename the selected keys on the fly.
What I’m aiming to achieve is a generic function, let’s call it select()
, that accepts two inputs:
- an object of data
- an object in which keys represent the desired new name, and values specify the path to desired properties.
For example, consider the following 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 }, }, };
My goal is to call select()
this way:
const earthDataSubset = select(earthData, { distanceFromSun: ['distanceFromSun'], asiaPop: ['continents', 'asia', 'population'], americaArea: ['continents', 'america', 'area'], japanTemp: ['continents', 'asia', 'countries', 'japan', 'temperature'], });
where the resulted earthDataSubset
is
// earthDataSubset { distanceFromSun: 149280000, asiaPop: 4560667108, americaArea: 42549000, japanTemp: 62.5 }
At this point, one may ask why I don’t simply do this:
const earthDataSubsetSimple = { distanceFromSun: earthData.distanceFromSun, asiaPop: earthData.continents.asia.population, americaArea: earthData.continents.america.area, japanTemp: earthData.continents.asia.countries.japan.temperature }
This won’t work because typically, my data arrives as an array of objects, so I need to map over the array and apply the same select procedure such as in:
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]
Admittedly, I could have called .map()
array method simply as:
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, }));
And get the desired output:
// [ { distanceFromSun: 149280000, // asiaPop: 4560667108, // americaArea: 42549000, // japanTemp: 62.5 }, // { distanceFromSun: 149280000, // asiaPop: 4560767108, // americaArea: 42549000, // japanTemp: 73.6 } ]
Nevertheless, I’m looking to create my own generic select()
function that accepts one object, and subsets it. The benefit of such approach is its flexibility. I can use it standalone on a single object, plus allowing me to scale select()
to an array of objects when needed, by doing something like:
// pseudo code myEarthArr.map( (record) => select(record, { distanceFromSun: ['distanceFromSun'], asiaPop: ['continents', 'asia', 'population'], americaArea: ['continents', 'america', 'area'], japanTemp: ['continents', 'asia', 'countries', 'japan', 'temperature'], }) )
From looking around in StackOverflow posts, I found this one 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 offers a ton of solutions for the simple scenario of subsetting, but none is addressing the issue of nested properties.
Advertisement
Answer
You can do something like this
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)
Explanation inner reduce part
path.reduce((current, segment) => current[segment] ?? undefined , data)
path is an array of property nested inside data
path.reduce cycle all these property name
example
path = ['continents', 'asia', 'population']
in the first iteration
- current is data your object (I omit it because it’s a bit long)
- segment is ‘continents’
- return data[‘continents’]
second iteration
- current is data[‘continents’]
- segment is ‘asia’
- return data[‘continents’][‘asia’]
You got the idea