5

I am looking to recreate the following as seen below, dynamically without having manually define where the matches are in the Object's properties.

Desired Outcome

const obj = {
  levelOne: {
    someFun: () => {},
    levelTwo: {
      anArray: [],
      something: 'asas',
      levelThree: {
        stay: 'the same',
        name: 'Set this one!',
      },
    },
  },
}

const updatedObj = {
  ...obj,
  levelOne: {
    ...obj.levelOne,
    levelTwo: {
      anArray: [],
      ...obj.levelOne.levelTwo,
      levelThree: {
        ...obj.levelOne.levelTwo.levelThree,
        name: 'Updated prop!',
      },
    },
  },
}

console.info(updatedObj)
{
 levelOne: {
   someFun: () => {},
   levelTwo: {
     something: 'asas',
       levelThree: {
         stay: 'the same',
         name: "Updated prop!",
       },
     },
   },
 }

So far

const inputOriginal = {
  levelOne: {
    levelTwo: {
        something: 'asas',
        levelThree: {
        name: "Original input!"
      }
    }
  }
}
const newInput = {
  levelOne: {
    levelTwo: {
      levelThree: {
        name: "Updated prop!"
      }
    }
  }
}

const mergeObjects = function(overWrite, original){
    return Object.entries(original).reduce( (acc, [ key, item ]) => {
        if(typeof item === 'object' && overWrite[key]) {
            mergeObjects(overWrite[key], item)
            acc = {
                ...acc,
                [key]: item
            }
        } else if(overWrite[key]) {
            acc = {
                ...acc,
                [key]: overWrite[key]
            }
        }
        return acc
    }, {})
}
const merged = mergeObjects(inputOriginal,newInput) //merged.levelOne.levelTwo.levelThree = "Updated prop!"

However I can see there is an error logic in my code that, when it comes back out of the recursion, it overwrites the changes values with its self, which has the original values.

How can I create a function that will do the same as 'Desired Outcome'?

Jamie Hutber
  • 26,790
  • 46
  • 179
  • 291
  • have you had a look at https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty ? – iLuvLogix Dec 20 '19 at 14:19
  • Ah interesting, so always overwrite it with the new proper inside the last `recursed` item and only if it is not its on property I guess? – Jamie Hutber Dec 20 '19 at 14:26
  • Ok, your question is confusing me, I can see what is the desired outcome, but what should be the desired input? If the desired input is obj and updatedObj, why are they on the desired outcome section? And after executing it, doesn't it do what you already wanted it to do? – Rainb Dec 23 '19 at 00:41
  • by the way it seems mergeObjects(overWrite[key], item) is doing nothing, because the result isn't being stored anywhere – Rainb Dec 23 '19 at 00:59
  • 1
    Please post some samples of `overWrite` and `original` objects – adiga Dec 23 '19 at 06:07
  • I am sorry guys, I have updated the example! Doh – Jamie Hutber Dec 23 '19 at 09:26
  • @Rainb the result is being stored in `merged` For this example just output `merged` – Jamie Hutber Dec 23 '19 at 09:33
  • Rainb is speaking about the mergeObject function called inside of itself. The output of the function has to be stored in the "item" variable: item=mergeObjects(overWrite[key], item). But it's not enough to make your function work. – Raphael Deiana Dec 23 '19 at 14:57
  • 5
    How does this differ from any of the other questions about deep merge? Like [How to deep merge instead of shallow merge?](https://stackoverflow.com/q/27936772/215552) – Heretic Monkey Dec 23 '19 at 15:34
  • Why isn't `anArray` in your updateObj ? – Nick Parsons Dec 24 '19 at 05:41

2 Answers2

4

You can try with this function :

const mergeObjects = (obj, updates) => {
  for (const key of Object.keys(updates)) {
    if (updates[key] instanceof Object) Object.assign(updates[key], mergeObjects(obj[key], updates[key]))
  }
  Object.assign(obj || {}, updates)
  return obj
}

Here is a working example using the given objects :

const inputOriginal = {
  levelOne: {
    levelTwo: {
      something: 'asas',
      levelThree: {
        name: "Original input!"
      }
    }
  }
}
const newInput = {
  levelOne: {
    levelTwo: {
      levelThree: {
        name: "Updated prop!"
      }
    }
  }
}

const mergeObjects = (obj, updates) => {
  for (const key of Object.keys(updates)) {
    if (updates[key] instanceof Object) Object.assign(updates[key], mergeObjects(obj[key], updates[key]))
  }
  Object.assign(obj || {}, updates)
  return obj
}

const merged = mergeObjects(inputOriginal, newInput)
console.log(merged);
Raphael Deiana
  • 750
  • 4
  • 20
0

how about using lodash

_.has(obj, 'l1.l2.l3' ) && _.set(obj, 'l1.l2.l3', 'I am now updated')

or

_.has(obj, 'l1.l2' ) && _.set(obj, 'l1.l2.l3', 'I am now updated')

I guess you can require object sub package only instead of the full lodash

Nidhin David
  • 2,426
  • 3
  • 31
  • 45