Link to home
Start Free TrialLog in
Avatar of roger v
roger vFlag for United States of America

asked on

jQuery appending new key/value pair to JSON object in a nested loop

I'm using jQuery 3.2.1 for calling an API service using $.ajax (type: "get"). The response object that is returned is in JSON format and is named responseList. I need to compare this to a JSON object that is stored in browser in a hidden field.

I'm trying to compare authorList with responseList, and if there is changed value in responseList for the same element, then populate another JSON object with the element and the changed value, along with another key/value pair to store a message. I'm able to populate the changedList object but the new key/value pair is being created as a new element rather than a new key/value pair WITHIN the element in changedList. Is there a clean way of performing this? Also, there is some error in how the array push() is being implemented in my code, because instead of 2 rows in changedList, I'm getting 15 objects, w/ triplicates of each JSON element. Below is my code:

var authorlist = [{"AUTHOR":"DONNA EDWARDS","COUNTRY":"USA","REGION":"MIDWEST"},{"AUTHOR":"EMERALD JONES","COUNTRY":"UK","REGION":"EU"},{"AUTHOR":"SHAKESPEARE","COUNTRY":"UK","REGION":"EU"}];

var responseList = [{"AUTHOR":"DONNA EDWARDS","COUNTRY":"CANADA","REGION":""},{"AUTHOR":"EMERALD JONES","COUNTRY":"SOUTH AFRICA","REGION":""},{"AUTHOR":"SHAKESPEARE","COUNTRY":"UK","REGION":"EU"}, {"AUTHOR":"JOHN DOE","COUNTRY":"AUSTRALIA","REGION":"APAC"}, {"AUTHOR":"KELLY YU","COUNTRY":"SINGAPORE","REGION":"APAC"}];

var changedList = [];
var messageHeader = "MSG";
var message = "Author's COUNTRY was updated!";
//outer loop
$.each(authorList, function(a, al){
	$.each(responseList, function(b, rl){
  	if(al.AUTHOR !== 'NULL'){
    		//check that COUNTRY in responseList is same as COUNTRY in authorList
        //if not same, then populate changedList with data
        changedList.push(rl);
        changedList.MSG = messageHeader;
        changedList.MESSAGE = message;
    }
  });
});

console.log(changedList);
//expected result for changedList should be as following:
//changedList = [{"AUTHOR":"DONNA EDWARDS","COUNTRY":"CANADA","REGION":"", "MSG": "Author's COUNTRY was updated"},{"AUTHOR":"EMERALD JONES","COUNTRY":"SOUTH AFRICA","REGION":"", "MSG": "Author's COUNTRY was updated"}];

Open in new window


As evident from the code above, there are 2 elements in changedList since the COUNTRY value for those 2 elements was changed in responseList compared to the values in authorList. In addition to populating these values in changedList, I also need to append "MSG" : "Author's COUNTRY was updated" key/value pair (I'm guessing that's what these are?) to the end of each element in changedList.

This is the fiddle:

http://jsfiddle.net/damon_matt/a6bf9ka2/
ASKER CERTIFIED SOLUTION
Avatar of Scott Fell
Scott Fell
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of roger v

ASKER

Scott,

Yes realized that after I posted the question - changed and updated, but for some reason fiddle didn't save the change. The logic inside my inner loop is messed up - it's creating 3 (number of elements that match in both json objects) and multiplies it by 5 and creates 15 separate objects in changedList json array.
I am going to be tied up for a while.  I think this is one of those times to not reinvent the wheel. I found this solution using underscore which seems easy to follow.    http://jsfiddle.net/drzaus/9g5qoxwj/


<script src="http://underscorejs.org/underscore-min.js"></script>
<div id="debug"></div>

Open in new window

(function(_) {
  function deepDiff(a, b, r, reversible) {
    _.each(a, function(v, k) {
      // already checked this or equal...
      if (r.hasOwnProperty(k) || b[k] === v) return;
      // but what if it returns an empty object? still attach?
      r[k] = _.isObject(v) ? _.diff(v, b[k], reversible) : v;
    });
  }
  
  /* the function */
  _.mixin({
    shallowDiff: function(a, b) {
      return _.omit(a, function(v, k) {
        return b[k] === v;
      })
    },
    diff: function(a, b, reversible) {
      var r = {};
      deepDiff(a, b, r, reversible);
      if(reversible) deepDiff(b, a, r, reversible);
      return r;
    }
  });

  /* just so there's something to see */
  function dump() {
    var args = Array.prototype.slice.call(arguments);
    console.log.apply(console, args);

    var p = document.createElement('pre');
    _.each(args, function(a) {
      p.innerText += JSON.stringify(a) + '  ';
    });
    document.getElementById('debug').appendChild(p);
  }

  var o1 = {
      a: 1,
      b: 2,
      c: 2
    },
    o2 = {
      a: 2,
      b: 1,
      c: 2
    },
    o3 = {
      a: 1,
      b: 2
    };


  dump('a = ', o1);
  dump('b = ', o2);
  dump('c = ', o3);
  dump('shallowdiff(a, b) = ', _.shallowDiff(o1, o2));
  dump('shallowdiff(b, a) = ', _.shallowDiff(o2, o1));
  dump('shallowdiff(a, c) = ', _.shallowDiff(o1, o3));
  dump('shallowdiff(c, a) = ', _.shallowDiff(o3, o1));

  dump('diff(a, b) = ', _.diff(o1, o2));
  dump('diff(b, a) = ', _.diff(o2, o1));
  dump('diff(a, c) = ', _.diff(o1, o3));
  dump('diff(c, a) = ', _.diff(o3, o1));
  dump('diff(c, a, reversible) = ', _.diff(o3, o1, true));

  var o3 = {
      x: 4,
      y: 2,
      z: o1,
      zz: o1
    },
    o4 = {
      x: 56,
      y: 2,
      z: o1,
      zz: o2
    };

  dump('o3 = ', o3);
  dump('o4 = ', o4);

  dump('shallowdiff(o3, o4) = ', _.shallowDiff(o3, o4));
  dump('shallowdiff(o4, o3) = ', _.shallowDiff(o4, o3));

  dump('diff(o3, o4) = ', _.diff(o3, o4));
  dump('diff(o4, o3) = ', _.diff(o4, o3));

  var o5 = {
      x: 4,
      y: 2,
      z: {
        a: 1,
        b: 2,
        c: 2
      },
      zz: {
        a: 1,
        b: 2,
        c: 2
      }
    },
    o6 = {
      x: 56,
      y: 2,
      z: {
        a: 1,
        b: 2,
        c: 2
      },
      zz: {
        a: 2,
        b: 1,
        c: 2
      }
    };

  dump('a = ', o5);
  dump('b = ', o6);

  dump('shallowdiff(a, b) = ', _.shallowDiff(o5, o6));
  dump('shallowdiff(b, a) = ', _.shallowDiff(o6, o5));

  dump('diff(a, b) = ', _.diff(o5, o6));
  dump('diff(a, b) = ', _.diff(o6, o5));

})(_.noConflict());

Open in new window

This one too

https://codepen.io/HipsterZipster/pen/BzyGWd

https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.12.0/lodash.min.js

Open in new window


var last = {
	"authed": true,
	"inForeground": true,
	"goodConnection": false,
	"inExecutionMode": false,
	"online": true
};

var curr = {
	"authed": true,
	"inForeground": true,
	"goodConnection": true,
	"inExecutionMode": false,
	"online": true
};

var diff = _.omitBy(curr, function(v, k) {
	console.log('k,v,last[k] = ' + k + ',' + v + ',' + last[k]);
	return last[k] === v;
});

console.log('diff='+JSON.stringify(diff));

// appStatus.service.js:88 AppStatusService: status {
// 	"authed": true,
// 	"inForeground": true,
// 	"goodConnection": false,
// 	"inExecutionMode": false,
// 	"online": true
// }
// 2016-06-02 00:26:44.885 appStatus.service.js:79 AppStatusService: updateAppStatus (key, val): goodConnection true
// 2016-06-02 00:26:44.886 fx.controller.js:62 FxController: App status (filtered: inExecutionMode): false
// 2016-06-02 00:26:44.887 appStatus.service.js:88 AppStatusService: status {
// 	"authed": true,
// 	"inForeground": true,
// 	"goodConnection": true,
// 	"inExecutionMode": false,
// 	"online": true
// }
// 2016-06-02 00:26:44.906 mobileConnection.service.js:28 MobileConnectionService: AppStatus  {
// 	"authed": true,
// 	"inForeground": true,
// 	"goodConnection": true,
// 	"inExecutionMode": false,
// 	"online": true
// }

Open in new window

SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of roger v

ASKER

I'm going to read up on lodash & _.reduce() - I have no clue what either are.
Avatar of roger v

ASKER

1. Is the logic for the nested loop that I had flawed which is why there were multiple objects being created in changedList? Is that because the inner object is not being 'compared' to the outer?

2. Are lodash & underscore interchangeable names for the same lib or are they separate libs?

3. Which one would be preferable efficiency wise and learning curve wise?

thanks
Avatar of roger v

ASKER

I made the following code change and am now getting the right value - I checked the AUTHOR value in the inner loop, and if it is the same, then I do the .push():
$.each(authorList, function(a, al){
	$.each(responseList, function(b, rl){
  	if(al.AUTHOR == rl.AUTHOR){
    	//found = true;
      if(al.COUNTRY !== rl.COUNTRY){
    		//check that COUNTRY in responseList is same as COUNTRY in authorList
        //if not same, then populate changedList with data
        changedList.push(rl);
        changedList.MSG = messageHeader;
        changedList.MESSAGE = message;
    	}
    }
    	
  	
  });
});

Open in new window

http://jsfiddle.net/damon_matt/a6bf9ka2/2/

I'm not sure if this is the right way or to use underscore/lodash to do the comparison.
I have never used loadash before and only found it from that codepen example.  If your end goal is to use two objects or arrays and make one new, then perhaps union.
http://jsbin.com/conabuduqo/edit?js,console
  <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.12.0/lodash.min.js"></script>
<script>
var authorlist = [{"AUTHOR":"DONNA EDWARDS","COUNTRY":"USA","REGION":"MIDWEST"},{"AUTHOR":"EMERALD JONES","COUNTRY":"UK","REGION":"EU"},{"AUTHOR":"SHAKESPEARE","COUNTRY":"UK","REGION":"EU"}];
var responseList = [{"AUTHOR":"DONNA EDWARDS","COUNTRY":"CANADA","REGION":""},{"AUTHOR":"EMERALD JONES","COUNTRY":"SOUTH AFRICA","REGION":""},{"AUTHOR":"SHAKESPEARE","COUNTRY":"UK","REGION":"EU"}, {"AUTHOR":"JOHN DOE","COUNTRY":"AUSTRALIA","REGION":"APAC"}, {"AUTHOR":"KELLY YU","COUNTRY":"SINGAPORE","REGION":"APAC"}];

var union = _.union(authorlist, responseList);

union.forEach(function(element) {
    console.log(element.AUTHOR+'|'+element.COUNTRY+'|'+element.REGION);
});


</script>

Open in new window

The output is

"DONNA EDWARDS|USA|MIDWEST"
"EMERALD JONES|UK|EU"
"SHAKESPEARE|UK|EU"
"DONNA EDWARDS|CANADA|"
"EMERALD JONES|SOUTH AFRICA|"
"SHAKESPEARE|UK|EU"
"JOHN DOE|AUSTRALIA|APAC"
"KELLY YU|SINGAPORE|APAC"
The above will only test if something has been added and make new. If there is a possibility that DONNA EDWARDS gets changed to DONNA SMITH, then this will not create the expected results.
Avatar of roger v

ASKER

Scott,

That jsbin example still doesn't address the problem of having duplicates.
I'm going to read up on lodash & _.reduce() - I have no clue what either are
lodash is a JavaScript library with some very efficient and fast routines for working with arrays and objects. Chances are if you want to do something with arrays and / or objects then lodash either has the full solution or the tools to help you build the solution a lot easier.
I'm only looking at this for the first time and maybe Julian has a better idea. But I would start with first defining what the rules are.   Assuming you start with 2 author's
var list1 =[{"AUTHOR":"DONNA"},{"AUTHOR":"EMERALD"}];

Open in new window

and assuming that anything in the current list will not change and there will be a 2nd list that can contain some of what is is in list 1 and / or new data
var list2 =[{"AUTHOR":"EMERALD"}, {"AUTHOR":"SHAKESPEARE"}];

Open in new window


You can create one combined list with union and then pair that down with unique.

var list1 =[{"AUTHOR":"DONNA"},{"AUTHOR":"EMERALD"}];
var list2 =[{"AUTHOR":"EMERALD"}, {"AUTHOR":"SHAKESPEARE"}];

var union = _.union(list1,list2);
var uniqWith = _.uniqWith(union, _.isEqual);

uniqWith.forEach(function(element) {
    console.log(element.AUTHOR);
});

Open in new window

http://jsbin.com/bojovijobe/1/edit?js,console
if there is changed value in responseList for the same element, then populate another JSON object with the element and the changed value,

Is the name the unique value you match on?