4

I need to synch two javascript objects I would like to fill object2 with the missing keys from object1 without replacing the existing ones, even for the nested ones:

var object1 = {
    firstName: "Marco",
    lastName: "Rossi",
    favoriteFood: {firstCourse: "pasta", secondCourse: "salsiccia"},
    favoriteDrink: "Vino",
    favoriteSong: "O sole mio"
}

var object2 = {
    firstName: "Marco",
    lastName: "Rossi",
    favoriteFood: {firstCourse: "pasta"},
    favoriteSong: "Viaggiare"
}

I don't really know how to go into the nested keys. Especially to keep checking for inner nests, what if you have 5 nests down or something like that I know how do deal with the first level but not further down.

The desired result for objec2 would be

var object2 = {
    firstName: "Marco",
    lastName: "Rossi",
    favoriteFood: {firstCourse: "pasta", secondCourse: "salsiccia"},
    favoriteDrink: "Vino",
    favoriteSong: "Viaggiare"
}

Thanks in advance for your help.

Leonardo Amigoni
  • 2,237
  • 5
  • 25
  • 35

7 Answers7

8

You need to write recursive function to handle nested objects. Maybe something like this:

var object1 = {
    firstName: "Marco",
    lastName: "Rossi",
    favoriteFood: {firstCourse: "pasta", secondCourse: "salsiccia"},
    favoriteMovie: {rating: 7, title: "Monday"},
    favoriteDrink: "Vino",
    favoriteSong: "O sole mio"
}

var object2 = {
    firstName: "Marco",
    lastName: "Rossi",
    favoriteFood: {firstCourse: "pasta"},
    favoriteSong: "Viaggiare"
}

function fillObject(from, to) {
    for (var key in from) {
        if (from.hasOwnProperty(key)) {
            if (Object.prototype.toString.call(from[key]) === '[object Object]') {
                if (!to.hasOwnProperty(key)) {
                    to[key] = {};
                }
                fillObject(from[key], to[key]);
            }
            else if (!to.hasOwnProperty(key)) {
                to[key] = from[key];
            }
        }
    }
}

fillObject(object1, object2);

alert( JSON.stringify(object2, null, '    ') );

Note: if you wonder about this line Object.prototype.toString.call(from[key]) - this is to reliably check that value is an object, because typeof null also reports object.

dfsq
  • 191,768
  • 25
  • 236
  • 258
  • After testing this throughly there is one case where this doesn't work. If a nested key doesn't exist, it won't create it. Let's say favoriteFood didn't exist in object2, this code doesn't create one present in object1. I am still trying to understand why. – Leonardo Amigoni Oct 24 '14 at 12:55
  • Sure! Feel free to get back if you find other issues. – dfsq Oct 24 '14 at 13:20
  • this will fail wherever an expected object is `null`, eg if `favoriteFood` is null then it will remain `null`. if anyone knows a fix i would appreciate it. – r3wt Aug 28 '19 at 19:52
1
for(var prop in object1){
    if(object1.hasOwnProperty(prop)){
        if(!(prop in object2)){
            object2[prop] = object1[prop];
        }
    }
}
Vítor Marques
  • 349
  • 4
  • 11
  • 1
    And what about nested objects? – dfsq Oct 24 '14 at 10:38
  • I dont think this will work for nested objects. If both have the same object as key and some of the keys are missing in the object2, this code wont allow copying because of this : if(!(prop in object2)) – Fyre Oct 24 '14 at 10:54
1
function sync(source, target) {
    if(getType(source) !== 'object' || getType(target) !== 'object') {
        return;
    }
    for(var p in source) {
        if(getType(target[p]) === 'object') {
            if(getType(target[p]) !== 'object') {
                target[p] = {};
            }
            sync(source[p], target[p]);
        } else if(getType(target[p]) === 'null' || getType(target[p]) === 'undefined'){
            target[p] = source[p];
        } 
    }
};

function getType(arg) {
    if(arg === null) {
        return 'null';
    } else if(arg === undefined) {
        return 'undefined';
    }
    var type = Object.prototype.toString.call(arg);
    return type.slice(8, type.indexOf(']')).toLowerCase();
};
creeper
  • 499
  • 2
  • 17
1

This worked for me. I think its pretty simple. Just check if the property exists in object2 leave it or else copy it if its not a object. If it is an object recurse.

 (function(){
    console.log("EXECTURING");
    var object1 = {
        firstName: "Marco",
        lastName: "Rossi",
        favoriteFood: {firstCourse: "pasta", secondCourse: "salsiccia"},
        favoriteDrink: "Vino",
        favoriteSong: "O sole mio"
    };

    var object2 = {
        firstName: "Marco",
        lastName: "Rossi",
        favoriteFood: {firstCourse: "pasta"},
        favoriteSong: "Viaggiare"
    };

    for( key in object1) {
        if(object1.hasOwnProperty(key)) {
            copyToB(key, object1, object2);
        }
    }
})();

function copyToB(key, o1, o2) {
    if(typeof(o1[key]) !== "object") {
        if(typeof o2[key] === "undefined")
            o2[key] = o1[key];
        return;
    }

    var tempObj = o2[key];
    for(k in  o1[key]) {
        copyToB(k, o1[key], tempObj);
    }
    o2[key] = tempObj;
}
Fyre
  • 1,180
  • 8
  • 12
0

One way to do this, is to check the typeof() each and every object property and traverse through recursively.

theinvisible
  • 158
  • 11
0

You need to go through the complete object and ask for the variable type of the key/value pair. If it's an object, do it again.

Mini Example in this fiddle.

$.each(object1, function( index, value ) {        
    console.log( index + ": " + typeof(value) );
});

(I'll leave this up, but Crossfires version is quicker and complete already)

elpoto
  • 105
  • 1
  • 8
0

All of these answers will fail if a path in the target is null, and thus it is necessary to look 1 level deeper into the object before recursing and check if a target node will be null, and if so update it before recursing.

NOTE: My answer takes the arguments in a different order than everyone else. a is the item you are checking for missing keys, and b is the reference object with correct keys. my function also returns true/false as to whether any changes were made to a. it also copies over default values from b to a when key is missing.

function has (o,k) {
    return typeof o==='object'&&(o.hasOwnProperty(k)||o[k]!==undefined);
}

function recursiveCheck( a, b ) {
    var c = false;//whether we have changes. the calling scope wants to know
    // a is the object to check, b is the target
    for(let k in b) {
        if(!has(a,k)) {
            c=true;
            a[k] = b[k];
        }else{
            // if b[k] is an object, we recursively check on the children for changes
            // we only want to manipulate a[k] if b[k]=== object and not null!!!
            if(typeof b[k]==='object'&&b[k]!==null){
                for(let k2 in b[k]) {
                    if(a[k]===null){
                        a[k]=b[k];
                        c=true;
                    }
                    if( (!has(a[k],k2) || a[k][k2]===null) && typeof b[k][k2]==='object' && b[k][k2]!==null){
                        a[k][k2] = b[k][k2];
                        c=true;
                    }
                }
                const hasChange = recursiveCheck(a[k],b[k]);
                c = c || hasChange;
            }
        }
    }
    return c;
}

usage

var a = { foo: null, bar: true };//this is the object we are checking 
var b = { foo:{test:1,hello:false},bar: false}; //this is the object with correct keys and default values that should copy over to a when needed.

const didChange = recursiveCheck(a,b);

test cases that prove correct output

var target = {
    private:{
        has:false,
        cost:0
    },
    shared:{
        has:false,
        cost:0
    }
}

var inputs = [
    {},// true
    {private:null,shared:null},// true
    {private:{has:true},shared:{cost:50}},// true
    {private:{has:true,cost:500},shared:{has:false,cost:250}}// false
];

console.log('inputs---',inputs);

const results = inputs.map(item=>recursiveCheck(item,target));

console.log('inputs(transformed)---',inputs);

console.log('results---',results); // should be [true, true, true, false]
r3wt
  • 4,642
  • 2
  • 33
  • 55