-4

NOTE: Object.assign will not work because it only makes a shallow copy.

Looking at the source code for Ramda.js

https://github.com/ramda/ramda/blob/v0.25.0/source/set.js

I'm looking to write a simple function to that will take an object, a path, and a value and return a new object. Ideally the new copy is as effecient as possible.

https://www.youtube.com/watch?v=n5REbbvRYqQ&feature=youtu.be&t=15m3s

Suppose I have an arbitrary object, which could be this

const obj = {
  a: {
    b: {
       c: 1
    }
  }
  e: 4
}

Example of the result produced by the function

const obj2 = func(obj,['a','b','c'],99)

console.log(obj2)
// {
//  a: {
//    b: {
//       c: 99
//    }
//  }
//  e: 4
//}

console.log(obj)
// {
//  a: {
//    b: {
//       c: 1
//    }
//  }
//  e: 4
//}

The ideal solution would create a new object efficiently as such: https://www.youtube.com/watch?v=n5REbbvRYqQ&feature=youtu.be&t=15m3s

Therefore if obj2.e = 5 then obj.e === 5 because only the func created a copy of the branch being modified but kept all other references.

Babakness
  • 2,954
  • 3
  • 17
  • 28

2 Answers2

0

You can do this using reduce() method.

const obj = {"a":{"b":{"c":1}}}

function func(o, arr, val) {
  return arr.reduce(function(r, e, i) {
    return arr[i+1] ? (r[e] || {}) : r[e] = val
  }, o), o
}

const result = func(obj, ['a','b','c'], 99)
console.log(result)
Nenad Vracar
  • 118,580
  • 15
  • 151
  • 176
  • Note that the function returns the same modified object and not the new object. – Nenad Vracar Jan 29 '18 at 10:08
  • Sorry this does not provide a new copy of the original object. You are passing the same object into reduce that came into `func`, and the mutation is occurring on the same object. – Babakness Jan 29 '18 at 10:36
  • @NenadVracar Love the use of the comma operator. Took me a while to figure out how this works. @Babak, You can use `Object.assign` to create a new object from the given one – Chirag Ravindra Jan 29 '18 at 10:40
  • Ideally the solution would pass create an new object where all nodes reference the original object except where the mutation occurred, video for reference https://youtu.be/n5REbbvRYqQ?t=15m3s – Babakness Jan 29 '18 at 10:40
  • @Babak have put in edits using `Object.assign` and, alternatively, a combination of `JSON.parse` and `JSON.stringify`. Any of those methods should preserve immutability. Hope it helps! Good luck! – Chirag Ravindra Jan 29 '18 at 10:48
  • @Babak Basically take a look at this question https://stackoverflow.com/questions/122102/what-is-the-most-efficient-way-to-deep-clone-an-object-in-javascript, decide which way you want to use to clone object and then apply this solution. – Nenad Vracar Jan 29 '18 at 10:49
  • `function func(o, arr, val) { var copyObj = Object.assign({},o); return arr.reduce(function(r, e, i) { return arr[i+1] ? (r[e] || {}) : r[e] = val }, copyObj), copyObj }` – Chirag Ravindra Jan 29 '18 at 10:57
  • JSON.parse(JSON.stringify(obj)) works but very inefficient. – Babakness Jan 29 '18 at 10:57
  • Actually surprise Object.assign doesn't work. Any ideas why? – Babakness Jan 29 '18 at 10:59
  • @Babak make sure you clone your object before processing it. Have edited this answer but it's pending peer review. See comment above for a sample code snippet. – Chirag Ravindra Jan 29 '18 at 10:59
  • Very surprised, any one have an idea why Object.assign does not work here? – Babakness Jan 29 '18 at 11:04
  • @ChiragRavindra Using your code in the comments, it doesn't work – Babakness Jan 29 '18 at 11:04
  • I think the problem is that this misses cases where the property is not there before hand. `func({},['b','c'],4)` returns empty. This is not a problem with `Object.assign`. Trying to fix the edge case – Chirag Ravindra Jan 29 '18 at 11:06
  • @Babak Because it create only shallow copy of the object. – Nenad Vracar Jan 29 '18 at 11:07
  • No, https://stackoverflow.com/questions/33796749/ecma6-object-assign-doesnt-do-a-deep-copy Object.assign does not make a deep copy – Babakness Jan 29 '18 at 11:07
  • @NenadVracar The fact that `Object.assign` does not do a deep merge but that won't be a problem if the clone is done before processing it. – Chirag Ravindra Jan 29 '18 at 11:09
  • @ChiragRavindra No, it doesn't make a deep copy, even if done before hand – Babakness Jan 29 '18 at 11:10
  • @Babak What I am trying to say is that even the base version of the function which does not create a new object but mutates the existing one misses the case when the properties do not exist before hand. Try `func({},['b','c'],4)` on the original. If that is fixed, Object.assign willl work as expected – Chirag Ravindra Jan 29 '18 at 11:13
0

Tweaking @NenadVracar answer to handle edge case of missing pre-existing properties and creating a new object:

You can first clone the object and then use reduce to boil down an array to build the path as needed.

function func(o, arr, val) {
    var copyObj = JSON.parse(JSON.stringify(o));
    //OR
    //var copyObj = Object.assign({},o);
    return arr.reduce(function (r, e, i) {
        return arr[i + 1] ? (r[e] || (r[e] = {})) : r[e] = val
    }, copyObj), copyObj
}

My test cases:

func({},['b','c'],4)  // {"b":{"c":4}}
func({a:1},['b','c'],4)  // {"a":1,"b":{"c":4}}
func({a:1,b:{d:5}},['b','c'],4)  //{"a":1,"b":{"d":5,"c":4}}
func({a:1,b:{d:5,c:10}},['b','c'],4) //{"a":1,"b":{"d":5,"c":4}}
Chirag Ravindra
  • 4,760
  • 1
  • 24
  • 35