4

Merging nested objects it overides.

let target = { cache: 
              {curUser:
               { callingName: 'ch sairam', dateOfBirth: undefined, isTempDob: true, knowMarketPrefChange: true, email: 'sairamch3@gmail.com', gender: '',  livingIn: 'IN', uid: 'CrzpFL2uboaeGvMxXi5WQKSQsCr1', timeZone: undefined }, 
               names: [ [Object] ],   minPrice: '2500', maxPrice: '50000', market: 'CA', foundLovedOne: false,  }
             }
let source = { cache: 
              {curUser: 
               { isTempDob: true, knowMarketPrefChange: false, timeZone: 'Asia/Kolkata' },
               prefLanguage: 'en', market: 'IN',  minPrice: 2250, maxPrice: 45000, foundLovedOne: false, domainName: 'roo-fire.appspot.com', prodQueryPageNumber: 0, welcomeIntentShown: true }, 
              curContexts: [] }
 target = Object.assign({},target,source);

when print target it results

Object { cache: Object { curUser: Object { isTempDob: true, knowMarketPrefChange: false, timeZone: "Asia/Kolkata" }, prefLanguage: "en", market: "IN",   minPrice: 2250, maxPrice: 45000, foundLovedOne: false,  prodQueryPageNumber: 0, welcomeIntentShown: true }, curContexts: Array [] }

source override target, I want to get this results?

{ cache: 
              {curUser:
               { callingName: 'ch sairam', dateOfBirth: undefined, isTempDob: true, knowMarketPrefChange: false, email: 'sairamch3@gmail.com', gender: '', prefMarket: 'CA', livingIn: 'IN', uid: 'CrzpFL2uboaeGvMxXi5WQKSQsCr1', timeZone:'Asia/Kolkata' }, 
               prefLanguage: 'en',names: [ [Object] ], minPrice: '2250', maxPrice: '45000', market: 'CA', foundLovedOne: false, prodQueryPageNumber: 0, welcomeIntentShown: true },
              curContexts: [] }
04FS
  • 5,660
  • 2
  • 10
  • 21

4 Answers4

11

You could merge all propeties and use a recursive aproach for nested objects.

function merge(a, b) {
    return Object.entries(b).reduce((o, [k, v]) => {
        o[k] = v && typeof v === 'object'
            ? merge(o[k] = o[k] || (Array.isArray(v) ? [] : {}), v)
            : v;
        return o;
    }, a);
}

var target = { cache: { curUser: { callingName: 'ch sairam', dateOfBirth: undefined, isTempDob: true, knowMarketPrefChange: true, email: 'sairamch3@gmail.com', gender: '', livingIn: 'IN', uid: 'CrzpFL2uboaeGvMxXi5WQKSQsCr1', timeZone: undefined }, names: [['Object']], minPrice: '2500', maxPrice: '50000', market: 'CA', foundLovedOne: false } },
    source = { cache: { curUser: { isTempDob: true, knowMarketPrefChange: false, timeZone: 'Asia/Kolkata' }, prefLanguage: 'en', market: 'IN', minPrice: 2250, maxPrice: 45000, foundLovedOne: false, domainName: 'roo-fire.appspot.com', prodQueryPageNumber: 0, welcomeIntentShown: true }, curContexts: [] };

console.log([{}, target, source].reduce(merge));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
  • may you kindly explain this line o[k] = v && typeof v === 'object' ? I learnt that this is a recurive function, but i dont quite understand the logic under the hood @Nina Scholz – jason Mar 10 '23 at 10:09
  • 1
    `v && typeof v === 'object'` separates between having `null` or an object which is not `null`. `null` is in javascript an object but not [truthy](https://developer.mozilla.org/en-US/docs/Glossary/Truthy). – Nina Scholz Mar 10 '23 at 10:15
  • 1
    Why do we assign o[k] with null if v is null? What if o[k] in a , say having a value, then value would be replaced by a null? In other questions, why this recursive would work as in finally merging the results as expected? ? merge(o[k] = o[k] || (Array.isArray(v) ? [] : {}), v) – jason Mar 11 '23 at 19:32
  • we assign a primitive value if the value is `null` and not an object. – Nina Scholz Mar 11 '23 at 19:35
  • sorry, it doesn't make sense to me when v is null, we immediately assign o[k] with v; o[k] = v ; may i know if there is an easier explanation for this recursive handling – Ae Leung Mar 12 '23 at 09:03
  • 1
    here it is: every recursion need to hande en end situation where no other call of the funtion takes place. this is here wrapped in a check for having an object (not null) and returning eiter the merged objects or just a value. the return of the value stops the recursion. – Nina Scholz Mar 12 '23 at 09:14
  • Sorry, this may sounds dumb, i couldnt figure this base case out for long. may you explain how you come up with this base case ? – jason Mar 13 '23 at 09:33
  • @jason, what is "*this base case*"? – Nina Scholz Mar 13 '23 at 09:35
  • when v && typeof v === 'object' is false ,right? but why this base case is valid and after all iteration, it can ensure the result would be corrected? I tried to follow the logic and iterate by hands, but after one or two rounds.... it became so messy. So, I was doubt and I may miss sth. – jason Mar 14 '23 at 02:20
  • 1
    @jason, at some end, no object is available, then it returns the single primitive value. maybe it comes more clear, if you replace the ternary with an `if` statement and return the first `?` part and later return the second `:` part. another `if` or `else` is not necessary, because having a `return` stament before, the rest of the function is always the `else` part. – Nina Scholz Mar 14 '23 at 07:55
  • @Nina Scholz , I tried to manual to do it by hands again. say there is a case: target: {a1:rty' , a2:'qwe'} ; source: {a1:rty' , a2:'iop'} ; then result of 'a2' would become 'iop' ; may i know if it is expected? In additional, may i ask if the implementation of merge function in lodash is the same as yours? Again, thank you so much for your explanation, i really appreciate it – jason Mar 15 '23 at 02:52
  • 1
    @jason, your result is right/expected. i don't kow how loash is implemented. you are welcome. – Nina Scholz Mar 15 '23 at 06:31
4

Object.assign doesn't do a deep merge. To do a nested merge, I suggest using lodash

import _ = require('lodash');
target = _.merge(source, value2);

Alternately, you can copy both the objects and do an object.assign merge

target =Object.assign({},JSON.parse(JSON.stringify(source)), JSON.parse(JSON.stringify(target)))
SoWhat
  • 5,564
  • 2
  • 28
  • 59
1

You can use this advanced function for object/array deep merge (or deep assign). It's working like a charm.

export function deepAssignJSON(target, source, {isMutatingOk = false, isStrictlySafe = false} = {}) {

    // Returns a deep merge of source into target. Does not mutate target unless isMutatingOk = true.
    target = isMutatingOk ? target : clone(target, isStrictlySafe);
    for (const [key, val] of Object.entries(source)) {
        if (val !== null && typeof val === `object`) {
            if (target[key] === undefined) {
                target[key] = {};
            }

            // Even where isMutatingOk = false,
            // recursive calls only work on clones, so they can always safely mutate --- saves unnecessary cloning
            target[key] = deepAssignJSON(target[key], val, {isMutatingOk: true, isStrictlySafe});
        } else {
            target[key] = val;
        }
    }
    return target;
}

function clone(obj, isStrictlySafe = false) {

    // Clones an object. First attempt is safe. If it errors (e.g. from a circular reference),
    // 'isStrictlySafe' determines if error is thrown or an unsafe clone is returned. 
    try {
        return JSON.parse(JSON.stringify(obj));
    } catch(err) {
        if (isStrictlySafe) { throw new Error() }
        console.warn(`Unsafe clone of object`, obj);
        return {...obj};
    }
}
Kamran Taghaddos
  • 452
  • 1
  • 10
  • 22
  • This simple solution works great – Sudip Barman Jan 16 '23 at 09:55
  • I don’t understand this part: if (target[key] === undefined) { ; if it is undefined. Then shouldn’t the next step is assign value into target[key]? But right now, we only do target[key] = val when val is null, which i found it confusing. In fact shouldn’t we assign nothing when val is null? – jason Mar 11 '23 at 19:47
0

You could also use Ramda. it's an open source javascript util. which has much less deps that loadash.

And with Ramda, using the mergeDeepRight :

import { mergeDeepRight } from 'ramda';

result = mergeDeepRight(sourceObject, targetObject)

Object assign doesn't take nested values. which is the issue that you having at this moment.

Ido Bleicher
  • 709
  • 1
  • 9
  • 19