Skip to content
Advertisement

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

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

Advertisement