comparing two JavaScript objects - javascript

So, let's say i have object A and object B. Object A has a number of children where object B has a number of children that are the same. How do I find out what are the differences the ones missing in object B and the ones added in object A and then put them into their own object or two-dimensional array.
For example, the first array being those that are added to second, being subracted:
var changes = [["google.com", "yahoo.com"],["facebook.com", "bing.com"]]
I am trying compare a snapshot of stored bookmarks and the current bookmarks list using crossrider.

I believe this is a follow on from the following questions, and so I will combine them all into a single code example that runs in the background scope (background.js):
realtime with non-event programming,
crossrider: store snapshot of bookmarks in local database and compare to current bookmarks list
So for the getChanges function, I prefer to convert the bookmark trees into hash lists and then compare the lists for changes. In the following example, I use createHash to create the hash lists using cloneNode to create a shallow clone of the node objects, and then in getChanges I compare the hash lists for additions, modifications, and deletions:
appAPI.ready(function() {
// Poll every 30 seconds
setInterval(function() {
appAPI.db.async.get('prevBookmarks', function(value) {
// Load or initialize the previous bookmarks list
var prevBookmarks = (value) ? value : {};
// Get current bookmarks
appAPI.bookmarks.getTree(function(nodes) {
// Save bookmark hash for comparison in next interval
appAPI.db.async.set('prevBookmarks', createHash(nodes[0]));
// Get hash list of curent bookmarks
var currBookmarks = createHash(nodes[0]);
// Get changes between the lists
var changes = getChanges(prevBookmarks, currBookmarks);
// Post changes to your API server
appAPI.request.post({
url: http://yourAPIserver.com,
postData: changes,
contentType: 'application/json'
});
});
});
}, 30 * 1000);
// Function to create a hash list from a bookmark tree
function createHash(node) {
var hash = {};
if (typeof node === 'object') hash[node._id] = cloneNode(node);
if (node.isFolder && typeof node.children !== 'undefined' && node.children.length > 0) {
node.children.forEach(function(child) {
var childHash = createHash(child);
for (var key in childHash) {
if (!hash[key]) hash[key] = cloneNode(childHash[key]);
}
});
}
return hash;
}
// Function to create shallow clones of bookmark nodes
function cloneNode(node) {
var clone = appAPI.JSON.parse(appAPI.JSON.stringify(node));
delete clone.children;
delete clone.dateAdded;
return clone;
}
// Get changes between current and previous bookmark hash lists
function getChanges(prev, curr) {
// Initialize return object
var changes = {added:{}, modified:{}, removed:{}};
// Search for added or modified nodes
for (var key in curr) {
if (!prev[key])
changes.added[key] = curr[key];
else if (appAPI.JSON.stringify(prev[key]) !== appAPI.JSON.stringify(curr[key]))
changes.modified[key] = curr[key];
}
// Search for removed nodes
for (var key in prev) {
if (!curr[key])
changes.removed[key] = prev[key];
}
return changes;
}
});
Disclaimer: I am a Crossrider employee

If the two objects to be compared are both one-dimensional arrays, then simply use the set arithmetic functions in Underscore.js, such as _.difference and _.intersection.
Or, use the same logic, which for intersect (unoptimized) is as simple as:
array1.filter(function(v){return array2.indexOf(v)!==-1);});
If you're looking for a generalized way to find the diff between two arbitrary objects of any depth and complexity, this is not a well-defined problem.

Related

Duplicate an array in an object then update a value based on key - Javascript

I need help doing the following.
I need to duplicate an array, update a value and insert it into a new object.
My Code right now:
// Sample test values {name:'The initial value', altName:'a first Name;a second name'}
var allAltName = test.altName;//Test come from a forEach() Iteration
if (test.altName) {//First I check if ther is my parama altName
var b,
countAllAltName = allAltName.split(';'); //Here I split my parameter string based on ';'
if (countAllAltName.length > 0) {
for (b = 0; b < countAllAltName.length; b = b + 1) {
var originalName = {};//I create a new object
originalName = test;//I load my existing object into a blank object
if (!ret["Index"]) // I check if my final object Key exist
ret["Index"] = {}; // if not create new object
if (!ret["Index"]["Index"]) // check another key
ret["Index"]["Index"] = []; // if not create new
originalName.name = countAllAltName[b];//Update my new object originalName with new value
ret["Index"]["Index"].push(originalName); // push current element in the designated list
ret["Index"]["Index"].sort(function (a, b) {
return a.name.localeCompare(b.name);
});
console.log(ret);
}
}
}
Issue is ret contains the required Object keys,but all value of name in each aray have the same last value of altName
I console.log() at each step what is the value of originalNameit always looks good.
Why the end results failed, and where I'm overwriting my data.
When you write originalName = test, you tell to JS that originalName is an "alias" for test (both share the same reference).
The behaviour is what you change in originaleName, it's impacted in test and vice versa (behaviour true only for Array and Object).
If you want to do a real copy, the simplest way (but with restrictions) is :
originalName = JSON.parse(JSON.stringify(test));
Last things : var originalName = {} is not an Array but an Object. There are some important differences between them

Add values from one array to object with specified key & index

Im using the following code,
jQuery.each(aDataSel, function(index, oData) {
oPushedObject = {};
aSelectedDataSet.push(fnCreateEnt(aProp, oData, oPushedObject));
});
This is aSelectedDataSet values
and this is the values of OData
What I need is that before I do the push is to fill the listTypeGroup & listTypeGroupDescription (with the red arrow ) with values that Are inside the oData -> ListTypeGroupAssigment -> result (listTypeGroup & listTypeGroupDescription) , The index is relevant since I want to add just the value of the index in each iteration (since this code is called inside outer loop and the index determine the current step of the loop) ,How it can be done nicely?
The result contain 100 entries (always) and the a selected data will have 100 entries at the end...
Update :)
Just to be clear In the pic I show the values which is hardcoded for this run but the values can be any values, we just need to find the match between the both objects values...
I mean to find a match between to_ListTypeGroupAssigment in both object (which in this case exist ) and if in oData there is result bigger then one entry start with the matching ...
UPDATE2 - when I try Dave code the following happen for each entry,
This happen in the Jquery.extend line...any idea how to overcome this?
The following hard-coded of Dave:-) work perfect but I need generic code which doesnt refer to specific field name
jQuery.each(aDataSet, function(index, oData) {
oPushedObject = {};
fnCreatePushedEntry(aProperties, oData, oPushedObject);
var result = oData.to_ListTypeGroupAssignment.results[index];
oPushedObject.to_ListTypeGroupAssignment = {
ListTypeGroup: result.ListTypeGroup,
ListTypeGroupDescription: result.ListTypeGroupDescription
};
aSelectedDataSet.push(oPushedObject);
});
Im stuck :(any idea how to proceed here ?what can be wrong with the extend ?
should I use something else ? Im new to jQuery...:)
I think that this happen(in Dave answer) because the oData[key] is contain the results and not the specified key (the keyValue = to_ListTypeGroupAssignment ) which is correct but we need the value inside the object result per index...
var needValuesForMatch = {
ListTypeGroup: 'undefined',
ListTypeGroupDescription: 'undefined',
}
//Just to show that oPushedObject can contain additional values just for simulation
var temp = {
test: 1
};
//------------------This object to_ListTypeGroupAssigment should be filled (in generic way :) ------
var oPushedObject = {
temp: temp,
to_ListTypeGroupAssignment: needValuesForMatch
};
oPushedObject is one instance in aSelectedDataSet
and after the matching I need to do the follwing:
aSelectedDataSet.push(oPushedObject);
Is this what you're after:
OPTION ONE - DEEP CLONE FROM oData TO aSelectedDataSet
aSelectedDataSet.forEach(function(currentObject,index){
for (var childObject in currentObject) {
if (! currentObject.hasOwnProperty(childObject))
continue;
var objectToClone = oData[childObject]['results'][index];
if(objectToClone)
$.extend(true,currentObject[childObject],objectToClone);
}
});
Here is your data in a fiddle with the function applied: https://jsfiddle.net/hyz0s5fe/
OPTION TWO - DEEP CLONE FROM oData ONLY WHERE PROPERTY EXISTS IN aSelectedDataSet
aSelectedDataSet.forEach(function(currentObject,index){
for (var childObject in currentObject) {
if (! currentObject.hasOwnProperty(childObject))
continue;
if(typeof currentObject[childObject] !== 'object')
continue;
for(var grandChildObject in currentObject[childObject]) {
var objectToClone = oData[childObject]['results'][index][grandChildObject];
if(typeof objectToClone === 'object') {
$.extend(true,currentObject[childObject][grandChildObject],objectToClone);
} else {
currentObject[childObject][grandChildObject] = objectToClone;
}
}
}
Fiddle for option 2: https://jsfiddle.net/4rh6tt25/
If I am understanding you correctly this should just be a small change:
jQuery.each(aDataSel, function(index, oData) {
oPushedObject = {};
fnCreateEnt(aProp, oData, oPushObj);
//get all the properties of oData and clone into matching properties of oPushObj
Object.getOwnPropertyNames(oData).forEach(function(key) {
if (oPushObj.hasOwnProperty(key)) {
//oPushObj has a matching property, start creating destination object
oPushObj[key] = {};
var source = oData[key];
var destination = oPushObj[key];
//can safely assume we are copying an object. iterate through source properties
Object.getOwnPropertyNames(source).forEach(function(sourceKey) {
var sourceItem = source[sourceKey];
//handle property differently for arrays
if (Array.isArray(sourceItem)) {
//just copy the array item from the appropriate index
destination[sourceKey] = sourceItem.slice(index, index + 1);
} else {
//use jQuery to make a full clone of sourceItem
destination[sourceKey] = $.extend(true, {}, sourceItem);
}
});
}
});
aSelectedDataSet.push(oPushedObject);
});
It is unclear what exactly your fnCreateEnt() function returns though. I am assuming it is the populated oPushObj but it's not entirely clear from your question.

Creating a new array combining two existing objects through iteration with condition in jQuery

I have a main object consisting of two main properties, data which contains messages and included which contains the senders of the messages. I want to create a new Array called messages which will contain all the values of both objects but in a way that every object inside this array will consist of the data values adding the correct sender as property to each of them.
I am able to separate the main object to two different ones, one containing the data and the other containing the senders.
if (jsonAPI.data) {
$.each(jsonAPI.data, function(index, value) {
dataObj[index] = value;
});
}
if (jsonAPI.included) {
$.each(jsonAPI.included, function(index, value) {
senders[value.id] = value;
});
}
I guess I have to make an iteration for every value of the dataObj and check if the relationships.sender.data.id is equal to senders.id then add the new property to dataObj, but I don't know how to write it.
What I say can be more clear in this fiddle https://jsfiddle.net/mosmic/f2dzduse/
Working jsfiddle: https://jsfiddle.net/f2dzduse/5/
var jsonAPI = {<snip>};
var dataObj = {};
if (jsonAPI.data) {
$.each(jsonAPI.data, function(index, value) {
dataObj[index] = value;
});
}
$.each(dataObj, function(index, value) {
//Prevent error if there is no sender data in included
if(jsonAPI.included.length - 1 >= index) {
//check if ids are equal
if(value.relationships.sender.data.id == jsonAPI.included[index].id) {
value.sender = jsonAPI.included[index];
}
}
});
console.log(dataObj);
This code assumes that jsonAPI.data.relationships.sender.data.id and jsonAPI.included.id are both in the same order!
If this is not always the case let me know and I'll rewrite the code to loop trough each jsonAPI.data and then loop trough jsonAPI.include to check for an equal id. This code will be slower since it will loop a total of jsonAPI.data.length X jsonAPI.include times.
Here's the updated code: https://jsfiddle.net/f2dzduse/6/
var jsonAPI = {<snip>};
var dataObj = [];
$.each(jsonAPI.data, function(x, data) {
dataObj[x] = data;
$.each(jsonAPI.included, function(y, included) {
if(data.relationships.sender.data.id == included.id) {
dataObj[x].sender = included;
}
});
});
console.log(dataObj);

If Object key exists add another object to it

I'm parsing a fairly large JSON file and doing some key:value pairs within an object. Issue I'm having is if I find a key I need to actually add another object to it INSTEAD of writing over it.
Example:
var collection = {};
angular.forEach(things, function(thing) {
collection[thing.Id] = thing.stuff;
//thing.stuff is an object
});
Came to a conclusion after some of the comments I've received in the first post:
var collection = {};
angular.forEach(things, function(thing) {
if(collection[thing.Id]){
//Key Exists push into array
collection[thing.Id].push(thing.stuff);
}else{
//Key doesn't exist create array for object
collection[thing.Id] = [thing.stuff];
}
});
In Modern way: maybe someone will come in handy
var collection = {};
angular.forEach(things, function(thing) {
if(!collection[thing.Id]){
collection[thing.Id] = [];
}
collection[thing.Id] = [...collection[thing.Id], thing.stuff];
// or ----------------------------------------------------
// add item at start
// collection[thing.Id] = [thing.stuff, ...collection[thing.Id]];
// or ---------------------------------------------
// if you doesn't want to change referrance every time
// collection[thing.Id].push(thing.stuff);
});

Get all items in NotesXSPDocument

In my Notes Database, I perform an audit when the document is saved. Pretty easy in LotusScript. I grab the original document (oDoc) from the server, then in the document I modified (mDoc), I do a Forall loop that gets the names of each item; forall item in mDoc.items. Grab the same item from oDoc, execute a function with the new item as an argument that will run down a case statement that will see if its a field we care about. if so, I update a set of list values in the document with "When", "Who", "What field", and the "New Value".
I'm doing this in a server side script. In trying this, I discovered a couple of interesting things;
currentDocument is the NotesXSPDocument that contains everything that was just changed.
currentDocument.getDocument() contains the pre-change values. It also returns a NotesDocument which has the "items" field that I can run through.
Thing is, I need something similar in the NotesXSPDocument. Is there a way in an iterative loop to grab the names and values of all items from there?
Here's the broken code. (Currently it's walking through the NotesDocument items, but those are the old values. I'd rather walk down the XSP document items)
function FInvoice_beginAudit() {
var original_doc:NotesDocument = currentDocument.getDocument();
var oItem:NotesItem;
var oItems:java.util.Vector = original_doc.getItems();
var iterator = oItems.iterator();
while (iterator.hasNext()) {
var oItem:NotesItem = iterator.next();
item = currentDocument.getItemValue(oItem.getName());
if (oItem == undefined) {
var MasterItem = ScreenAudit(doc,item,True)
if (MasterItem) { return true }
} else {
if (item.getValueString() != oItem.getValueString()) {
var MasterItem = ScreenAudit(doc,Item,True);
if (MasterItem) { return true }
}
}
}
}
You can get both versions of a document after submit - the original and the one with changed/new values:
original: var original_doc:NotesDocument = currentDocument.getDocument();
changed: var changed_doc:NotesDocument = currentDocument.getDocument(true);
This way you can compare the items for changes.
But, there is a pitfall: after assigning "changed_doc" to currentDocument.getDocument(true) the "original_doc" has the changed values too because both variables point to the same document. That's why we have to copy all items from currentDocument.getDocument() to a new temporary document first and only after get the changed values with currentDocument.getDocument(true). As an alternative you could read the original document from server like you do in LotusScript.
This is a code for detecting changed items as a starting point:
var original_doc:NotesDocument = database.createDocument();
currentDocument.getDocument().copyAllItems(original_doc, true);
var changed_doc:NotesDocument = currentDocument.getDocument(true);
var oItems:java.util.Vector = original_doc.getItems();
var iterator = oItems.iterator();
while (iterator.hasNext()) {
var oItem:NotesItem = iterator.next();
var itemName = oItem.getName();
var cItem:NotesItem = changed_doc.getFirstItem(itemName);
if (cItem.getText() !== oItem.getText()) {
print("changed: " + itemName);
}
oItem.recycle();
cItem.recycle();
}
original_doc.remove(true);
original_doc.recycle();

Categories