Modifying deeply nested objects using functional programming

2020-03-30

While doing some refactoring and clean-up of Division By Zero, I am adding getters and setters.

Making a getter in a functional style is of course trivial. Say we have an object, someObject, with some props:

const someObj = {
  a: {
    b: { 
      c: 1
    },
    d: 2
  },
  e: {
    d: 3
  }
};

A getter for the key d could look like this:

const getC = (obj) => obj.a.b.c;

But how should we make changes to deeply nested attributes? The naive solution for changing c to 5 would be embedded and not generic. It would look something like,

return {
  ...obj,
  a: {
    ...obj.a,
    b: {
      ...obj.a.b,
      c: 5
    }
  }
};

Using destructuring would make the solution cleaner, but still noisy.

A more functional approach would be to make a generic function for updating arbitrarily nested props of an object.

const deepSet = (path) => (v, obj) => {  
  const setValue = ([head, ...tail], o) => tail.length
    ? {
      ...o,
      [head]: setValue(tail, o[head])
    }
    : {
      ...o,
      [head]: v
    };
  return setValue(path, obj);
};

const setC = deepSet(['a', 'b', 'c']);

console.log(JSON.stringify(setC(5, someObject), null, 2));
//{
//  "a": {
//    "b": {
//      "c": 5
//    },
//    "d": 2
//  },
//  "e": {
//    "d": 3
//  }
//}

This following solution would be just as immutable as the previous one, but much cleaner. Using currying, we get generic functionality (if needed you could curry the last remaining parameters as well) supporting partial application,

const deepSet = (path) => (v, obj) => {  
  const setValue = ([head, ...tail], o) => tail.length
    ? {
      ...o,
      [head]: setValue(tail, o[head])
    }
    : {
      ...o,
      [head]: v
    };
  return obj === undefined
    ? (_obj) => setValue(path, _obj)
    : setValue(path, obj);
};

About | Archive