Home > database >  How to get the difference of two objects by subtracting properties, regardless of depth level?
How to get the difference of two objects by subtracting properties, regardless of depth level?

Time:04-20

I want to subtract the values of two objects of the exact same structure. Although one answer exists here, it's limited to objects of no-depth. In my case, I'm looking for a robust solution that would allow subtracting objects of any depth, as long as they're of the same structure.

Example

Consider the following two objects, earthData2022 and earthData2050:

const earthData2022 = {
  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 },
  },
};

Please note that both objects have:

  • exact same structure
  • all values are numbers, either integers or decimals. There are no strings or booleans, nor arrays.

I want to subtract: earthData2050 - earthData2022 to get a new object:

// desired output
// continents' areas aren't expected to change so their diff is `0`
// likewise, the distance of earth from sun
const earthDataDiff = {
  distanceFromSun: 0,
  continents: {
    asia: {
      area: 0,
      population: 100000,
      countries: { japan: { temperature: 11.1 } },
    },
    africa: { area: 0, population: 40000 },
    europe: { area: 0, population: 100 },
    america: { area: 0, population: -10000 },
    australia: { area: 0, population: 3000 },
    antarctica: { area: 0, population: 13 },
  },
};

As mentioned above, it's tempting to use the sweet answer given here:

function mySub(x, y) {
  return Object.keys(x).reduce((a, k) => {
    a[k] = x[k] - y[k];
    return a;
  }, {});
}

However, when calling mySub() we get this unsurprising output:

mySub(earthData2050, earthData2022)
// {"distanceFromSun":0,"continents":null}

My question, therefore, is how I can recursively subtract all entries, no matter how deep, provided that the objects have the same structure. Also, as I'm running this code on Node, I'm happy to utilize any new ECMAScript feature that may come in handy.

CodePudding user response:

recursion is your friend here

const earthData2022 = {
  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 },
  },
};


function mySub(x, y) {
  const result = {}
  Object.keys(x).forEach((key) => {
    if (typeof x[key] === 'number') {
      result[key] = x[key] - y[key]
    } else {
      result[key] = mySub(x[key], y[key])
    }
  });
  return result;
}

console.log(mySub(earthData2050, earthData2022));

CodePudding user response:

Declarative/functional solution:

const difference = (obj1, obj2) => Object.entries(obj1).reduce((t, [key, value]) => {
    const obj2Value = obj2[key];
    return {
        ...t,
        [key]: typeof value === "object" ?
            difference(value, obj2Value) :
            value - obj2Value
    };
}, {});

Explaination

Object.entries converts the object to a two-dimensional array of key value pairs. Using array.reduce to iterate over the pairs, it can reduce the array to an object. An analogy of this would be when you're reducing a broth to a sauce while cooking. If the value of the property is an object, the resulting property should be the difference of the difference of the sub-object (recursion). If not, it has to be a number and can therefore be subtracted.

Further reading:

  • Related