Objects deep/nested child-level comparison - javascript

I have two objects, I want to list changes between both as described below:
Currently I'm getting following output
current value || new value
title : Object 1 || title : Object 1 UPDATED
description : Object 1 Description || description : Object 1 Description UPDATED
Currently my code works for root level comparison(as highlighted above). But I am looking for comparisons on deep/nested child-level differences.
My output should look something like below
current value || new value
title : Object 1 || title : Object 1 UPDATED
description : Object 1 Description || description : Object 1 Description UPDATED
releations.tools[0].title: my first tool || releations.tools[0].title: my first tool UPDATED
relations.tools[0].types[1].name : test2 || DELETED
relations.training[0].description: training Description || relations.training[0].description: training Description UPDATED
relations.training[0].trainingTypes[1].name : in-person || DELETED
My current code
function diffObjects(obj1, obj2) {
let res = [];
let objKeysArray = _.keys(obj2) || [];
if (!obj1 || !obj2) {
return res;
}
if (objKeysArray.length === 0) {
return res;
}
_(objKeysArray).forEach((key) => {
console.log(obj1[key], obj2[key]);
if (_.isArray(obj1[key]) && _.isArray(obj2[key])) {
} else if (_.isObject(obj1[key]) && _.isObject(obj2[key])) {
} else if (!_.isEqual(obj1[key], obj2[key])) {
let change1 = `${key} : ${obj1[key]}`;
let change2 = `${key} : ${obj2[key]}`;
res.push({
currentVal: change1,
newVal: change2
});
}
});
return _.flattenDeep(res);
}
I have created a fiddle for above code here:
JSFiddle Link : https://jsfiddle.net/vr0pgemj/
I have already referenced these posts:
Deep comparison of objects/arrays
Javascript Deep Comparison
But they only give me TRUE or FALSE results and not the differences I am looking for.

I made a working fork of your fiddle here with ECMAscript6 syntax.
Here is an embedded version as well:
(function() {
'use strict';
function diffObj(obj1, obj2, ref) {
var prefix = ref || '';
var res = [];
if (!_.isUndefined(obj1) && _.isUndefined(obj2)) {
res.push({
currentVal: prefix + ' : ' + JSON.stringify(obj1),
newVal: 'DELETED'
});
} else if (_.isUndefined(obj1) && !_.isUndefined(obj2)) {
res.push({
currentVal: 'DELETED',
newVal: prefix + ' : ' + JSON.stringify(obj2)
});
}
if (_.isUndefined(obj1) || _.isUndefined(obj2)) {
return _.flattenDeep(res);
}
var keys = _.uniq(_.keys(obj1).concat(_.keys(obj2)));
_(keys).forEach(function(key) {
var value1 = obj1[key];
var value2 = obj2[key];
if (!_.isUndefined(value1) && _.isUndefined(value2)) {
res.push({
currentVal: prefix + key + ' : ' + value1,
newVal: 'DELETED'
});
} else if (_.isUndefined(value1) && !_.isUndefined(value2)) {
res.push({
currentVal: 'DELETED',
newVal: prefix + key + ' : ' + value2
});
} else if (_.isArray(value1) && _.isArray(value2)) {
var entries = Math.max(value1.length, value2.length);
for (var i = 0; i < entries; i++) {
res.push(diffObj(value1[i], value2[i], prefix + key + '[' + i + '].'));
}
} else if (_.isObject(value1) && _.isObject(value2)) {
res.push(diffObj(value1, value2, prefix + key + '.'));
} else if (!_.isEqual(value1, value2)) {
res.push({
currentVal: prefix + key + ' : ' + value1,
newVal: prefix + key + ' : ' + value2
});
}
});
return _.flattenDeep(res);
}
var json1 = {
"id": 1,
"title": "Object 1",
"description": "Object 1 Description",
"test": "foo bar",
"relations": {
"tools": [{
"id": 2,
"title": "my first tool",
"description": "tools description",
"types": [{
"id": 123,
"name": "test"
}, {
"id": 111,
"name": "test2"
}]
}],
"training": [{
"id": 3,
"title": "Test training",
"description": "training Description",
"trainingTypes": [{
"id": 1,
"name": "online"
}, {
"id": 2,
"name": "in-person"
}, {
"id": 3,
"name": "boot camp"
}]
}]
}
};
var json2 = {
"id": 1,
"title": "Object 1 UPDATED",
"description": "Object 1 Description UPDATED",
"relations": {
"tools": [{
"id": 2,
"title": "my first tool UPDATED",
"description": "tools description",
"types": [{
"id": 123,
"name": "test"
}]
}],
"training": [{
"id": 3,
"title": "Test training",
"description": "training Description UPDATED",
"trainingTypes": [{
"id": 1,
"name": "online"
}, {
"id": 3,
"name": "boot camp"
}]
}]
}
};
var res = diffObj(json1, json2);
res = res.map(function(d) {
return '<tr><td>' + d.currentVal + '</td><td>' + d.newVal + '</td></tr>';
});
$('#tableResult > tbody').append(res);
})();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.13.1/lodash.min.js"></script>
<table id="tableResult" class="table table-hover table-striped">
<thead>
<tr>
<th>
current
</th>
<th>
new
</th>
</tr>
</thead>
<tbody>
</tbody>
</table>

Related

Angular: How to get the count of the Object with specific value?

I have a JSON database with objects. Each one has properties with a specific assigned value: a, b or c.
[
{
"id": 1,
"category": "a"
},
{
"id": 2,
"category": "b"
},
{
"id": 3,
"category": "c"
},
{
"id": 4,
"category": "a"
},
{
"id": 5,
"category": "a"
},
{
"id": 5,
"category": "b"
}
]
I want to display something like:
There is a total of 6 items: a x 3, b x 2 and c x 1.
I know I have to use objectsinmyjsondatabase.length to get the total.
I'm wondering how is it possible to get the length (number) of objects that have a specific value?
Define a function:
getCount(character) {
return this.objects.filter(obj => obj.category === character).length;
}
and the call it as:
this.getCount('a');
One way to solve your problem is to use map-reduce. Here is a quick solution. Hope that will solve your problem.
var data = [
{
"id": 1,
"category": "a"
},
{
"id": 2,
"category": "b"
},
{
"id": 3,
"category": "c"
},
{
"id": 4,
"category": "a"
},
{
"id": 5,
"category": "a"
},
{
"id": 5,
"category": "b"
}
];
// First get list of all categories
var categories = data.map(function(x) { return x["category"]; });
// Count no of items in each category
var countByCategories = categories.reduce(function(x, y) {
if (typeof x !== "object") {
var reduced = {};
reduced[x] = 1;
reduced[y] = 1;
return reduced;
}
x[y] = (x[y] || 0) + 1;
return x;
});
// Final build string that you want
var categoryStrings = [];
for (var category in countByCategories) {
categoryStrings.push(category + ' x ' + countByCategories[category]);
}
var msg = 'There is a total of ' + categories.length + ' items: ';
if (categoryStrings.length > 2) {
msg = categoryStrings.slice(0, -1).join(', ');
msg += ' and ' + categoryStrings.slice(-1);
} else {
msg = categoryStrings.join(', ');
}
// print results
console.log(msg);
You easily can count objects which have specific value like this
let objects = [
{
"id": 1,
"category": "a"
},
{
"id": 2,
"category": "b"
},
{
"id": 3,
"category": "c"
},
{
"id": 4,
"category": "a"
},
{
"id": 5,
"category": "a"
},
{
"id": 5,
"category": "b"
}
];
var filtered = new Array();
objects.filter(function (t) {
var found = filtered.some(function (el, index) {
if (false == (t.category === el.category)) {
return false;
}
filtered[index].count = filtered[index].count + 1;
return true;
}, filtered);
if (false === found) {
filtered.push({category: t.category, count: 1});
}
}, filtered);
console.log('There is a total of ' + objects.length + ' items: '
+ filtered.map(function (t) {
return t.category + ' x ' + t.count;
})
);

Load a JSON document and sort it on one or more values in key:value pairs with JavaScript

I want to load an external JSON object and sort based on one or more key:value pairs.
For example using the JSON below I might need to sort on category1 and then type.
I've tried array.sort() but no matter what I throw at it my data is never sorted; It's always output in the same order as in the JSON file.
{
"books": [
{
"sku": "{1234}",
"title": "Moby Dick",
"type": "paperback",
"category1": "fiction",
"category2": "classic",
"category3": "",
"image": "",
"ctaLabel": "Learn More"
},
{
"sku": "{5678}",
"title": "1984",
"type": "hardcover",
"category1": "fiction",
"category2": "",
"category3": "",
"image": "",
"ctaLabel": "Learn More"
},
{
"sku": "{5678}",
"title": "Another Title",
"type": "paperback",
"category1": "nonfiction",
"category2": "youngadult",
"category3": "",
"image": "",
"ctaLabel": "Learn More"
}
]
}
$(function() {
$.getJSON('books.json', function (data) {
console.log(data);
var items = data.books.map(function (item) {
return item.sku + ': ' + item.title;
});
if (items.length) {
var content = '<li>' + items.join('</li><li>') + '</li>';
var list = $('<ul />').html(content);
$("#show-data").html(list);
}
});
});
Based on this answer, you can implement a multi-level sort as follows :
function multiLevelSort(arr, criteria) {
return arr.sort(function(x, y) {
return criteria.reduce(function(prev, curr) {
var dir = (curr.dir < 0) ? -1 : 1,
x_ = x[curr.prop],
y_ = y[curr.prop];
return prev || (x_ === y_ ? 0 : x_ > y_ ? dir : -dir);
}, 0);
});
}
or, with destructuring (in Node but not yet in all browsers) :
function multiLevelSort(arr, criteria) {
return arr.sort(function(x, y) {
return criteria.reduce(function(prev, {prop, dir}) {
dir = (dir < 0) ? -1 : 1;
var x_ = x[prop],
y_ = y[prop];
return prev || (x_ === y_ ? 0 : x_ > y_ ? dir : -dir);
}, 0);
});
}
where :
arr is an array of objects, as in the question.
criteria is an array of objects of the following format :
var criteria = [
{prop: "type", dir: 1}, // dir:1=ascending; dir:-1=descending
{prop: "category1", dir: 1},
{prop: "category2", dir: 1}
];
Then simply call :
multiLevelSort(myArray, myCriteria);
Like Array.prototype.sort(), myArray will be mutated and returned.
DEMO

Create Hashmap like implementation using java-script for json

I am looking to create hashmap like array which will contain key and value derived from another array which has nested objects.
so i am trying the following code.
var testhash={},data=[
{
"yang_type": "container",
"name": "c1",
"value": "",
"children": [
{
"yang_type": "container",
"name": "c2",
"value": "",
"children": [
{
"yang_type": "list",
"name": "Car",
"value": "",
"children": [
{
"yang_type": "leaf",
"name": "wheels",
"value": "",
"children": [
{
"name": "max-elements",
"value": "4",
"children": [],
"yang_type": ""
}
]
}
]
},
{
"yang_type": "",
"name": "text",
"value": "4",
"children": []
}
]
}
]
}
];
var k='';
function loop1(a, depth) {
var l,s='';
if(depth){s += '/';}
k=k+a.yang_type+a.name+a.value;
v=Array(depth + 1).join(s) + a.yang_type+a.name+a.value;
testhash.push(k:v);
//console.log(l);
//console.log(Array(depth + 1).join("/") + a.yang_type,a.name,a.value);
//hashServiceParams.push(Array(depth + 1).join("/") + a.yang_type,a.name,a.value);
Array.isArray(a.children) && a.children.forEach(function(child) {
loop1(child, depth + 1);
});
}
console.log(testhash);
The output, I am expecting is
{"containerc1":*,"containerc2":"containerc1/containerc2","listcar":"containerc1/containerc2/listcar","leafwheels":"containerc1/containerc2/listcar/leafwheels","max-elements":"containerc1/containerc2/listcar/leafwheels/max-elements","text4":"containerc1/text4"}
The above array will act as an hash map that contains key and value , where value stores the part of that data in the tree structure.
my code just calculates the depth and adds / to each level it moves down but i expect the output to be as shown above. Any recommendation coders ?
The following should do the trick (use either version according to your needs):
ECMAScript 6:
function parseData(data, prefix) {
let result = {};
data.forEach(o => {
const key = `${o.yang_type}${o.name}`;
result[key] = prefix ? `${prefix}/${key}` : '*';
if (o.children) {
const newPrefix = prefix ? `${prefix}/${key}` : key;
result = Object.assign(result, parseData(o.children, newPrefix));
}
});
return result;
}
ECMAScript 5:
function shallowMerge(obj1,obj2){
var obj3 = {};
for (var attrname in obj1) { obj3[attrname] = obj1[attrname]; }
for (var attrname in obj2) { obj3[attrname] = obj2[attrname]; }
return obj3;
}
function parseData(data, prefix) {
var result = {};
data.forEach(function (o) {
var key = '' + o.yang_type + o.name;
result[key] = prefix ? prefix + '/' + key : '*';
if (o.children) {
var newPrefix = prefix ? prefix + '/' + key : key;
result = shallowMerge(result, parseData(o.children, newPrefix));
}
});
return result;
}
In order to use it you simply need to do the following:
let testhash = parseData(data);
This will populate the testHash with the result you need.

How to get the key value from nested object

I am having below object where I am trying to get all the id values.
[{
"type": "test",
"id": "100",
"values": {
"name": "Alpha"
},
"validations": []
}, {
"type": "services",
"validations": [{
"id": "200",
"name": "John",
"selection": [{
"id": "300",
"values": {
"name": "Blob"
}
}]
}]
}]
Using the below code, I am getting only the first id value. Is there any way to get all the id values from the nested object without using any external module.
for (var prop in obj) {
console.log(prop)
if (prop === key) {
set.push(prop);
}
}
Expected Output
[100,200,300] //all id values
You can use a JavaScript function like below to get the nested properties:
function findProp(obj, key, out) {
var i,
proto = Object.prototype,
ts = proto.toString,
hasOwn = proto.hasOwnProperty.bind(obj);
if ('[object Array]' !== ts.call(out)) out = [];
for (i in obj) {
if (hasOwn(i)) {
if (i === key) {
out.push(obj[i]);
} else if ('[object Array]' === ts.call(obj[i]) || '[object Object]' === ts.call(obj[i])) {
findProp(obj[i], key, out);
}
}
}
return out;
}
Check this Fiddle for a working solution.
Using Object.keys
function findProp(obj, prop) {
var result = [];
function recursivelyFindProp(o, keyToBeFound) {
Object.keys(o).forEach(function (key) {
if (typeof o[key] === 'object') {
recursivelyFindProp(o[key], keyToBeFound);
} else {
if (key === keyToBeFound) result.push(o[key]);
}
});
}
recursivelyFindProp(obj, prop);
return result;
}
// Testing:
var arr = [{
"type": "test",
"id": "100",
"values": {
"name": "Alpha"
},
"validations": []
}, {
"type": "services",
"validations": [{
"id": "200",
"name": "John",
"selection": [{
"id": "300",
"values": {
"name": "Blob"
}
}]
}]
}];
console.log(findProp(arr, "id"));
To get the keys from nested objects, you first need to put your code in a function, then for each of the top-level keys, check if it's an array or object. If it is, just call your function again from within that function (weird, I know.) Just make sure you don't skip the check of whether it's an object. You'll get stuck in an infinite loop. Something like this:
function parseObjectKeys(obj) {
for (var prop in obj) {
console.log(prop)
var sub = obj[prop]
if (typeof(sub) == "object") {
parseObjectKeys(sub);
}
}
}
Here's a more complex example:
https://jsfiddle.net/tfqLnzLm/1/
You can use a XPath styled json parser like JSONPath. The version I'm presenting here is a extended version I did here:
function jsonPath(obj,expr,arg){var P={resultType:arg&&arg.resultType||"VALUE",result:[],normalize:function(e){var t=[];return e.replace(/[\['](\??\(.*?\))[\]']/g,function(e,r){return"[#"+(t.push(r)-1)+"]"}).replace(/'?\.'?|\['?/g,";").replace(/;;;|;;/g,";..;").replace(/;$|'?\]|'$/g,"").replace(/#([0-9]+)/g,function(e,r){return t[r]})},asPath:function(e){for(var t=e.split(";"),r="$",a=1,n=t.length;n>a;a++)r+=/^[0-9*]+$/.test(t[a])?"["+t[a]+"]":"['"+t[a]+"']";return r},store:function(e,t){return e&&(P.result[P.result.length]="PATH"==P.resultType?P.asPath(e):t),!!e},trace:function(e,t,r){if(e){var a=e.split(";"),n=a.shift();if(a=a.join(";"),t&&t.hasOwnProperty(n))P.trace(a,t[n],r+";"+n);else if("*"===n)P.walk(n,a,t,r,function(e,t,r,a,n){P.trace(e+";"+r,a,n)});else if(".."===n)P.trace(a,t,r),P.walk(n,a,t,r,function(e,t,r,a,n){"object"==typeof a[e]&&P.trace("..;"+r,a[e],n+";"+e)});else if(/,/.test(n))for(var l=n.split(/'?,'?/),s=0,c=l.length;c>s;s++)P.trace(l[s]+";"+a,t,r);else/^\(.*?\)$/.test(n)?P.trace(P.eval(n,t,r.substr(r.lastIndexOf(";")+1))+";"+a,t,r):/^\?\(.*?\)$/.test(n)?P.walk(n,a,t,r,function(e,t,r,a,n){P.eval(t.replace(/^\?\((.*?)\)$/,"$1"),a[e],e)&&P.trace(e+";"+r,a,n)}):/^(-?[0-9]*):(-?[0-9]*):?([0-9]*)$/.test(n)&&P.slice(n,a,t,r)}else P.store(r,t)},walk:function(e,t,r,a,n){if(r instanceof Array)for(var l=0,s=r.length;s>l;l++)l in r&&n(l,e,t,r,a);else if("object"==typeof r)for(var c in r)r.hasOwnProperty(c)&&n(c,e,t,r,a)},slice:function(e,t,r,a){if(r instanceof Array){var n=r.length,l=0,s=n,c=1;e.replace(/^(-?[0-9]*):(-?[0-9]*):?(-?[0-9]*)$/g,function(e,t,r,a){l=parseInt(t||l),s=parseInt(r||s),c=parseInt(a||c)}),l=0>l?Math.max(0,l+n):Math.min(n,l),s=0>s?Math.max(0,s+n):Math.min(n,s);for(var o=l;s>o;o+=c)P.trace(o+";"+t,r,a)}},eval:function(x,_v,_vname){try{return $&&_v&&eval(x.replace(/#/g,"_v"))}catch(e){throw new SyntaxError("jsonPath: "+e.message+": "+x.replace(/#/g,"_v").replace(/\^/g,"_a"))}}},$=obj;return expr&&obj&&("VALUE"==P.resultType||"PATH"==P.resultType)?(P.trace(P.normalize(expr).replace(/^\$;/,""),obj,"$"),P.result.length?P.result:!1):void 0}
// some extensions I have added to JSONPath
var jsonPathStore = function(obj,path,values) {
var maps=jsonPath(obj, path,{resultType:"PATH"})
maps.map(function(item,index) {
return eval( '(' + item.replace(/\$/,"obj") + '="' + values[index] +'"' + ')' );
})
}
var jsonPathDelete = function(obj,path) {
var maps=jsonPath(obj, path,{resultType:"PATH"})
maps.map(function(item,index) {
return eval( '(' + 'delete ' + item.replace(/\$/,"obj") + ')' );
})
}
var jsonPathRead = function(obj,path) {
var maps=jsonPath(obj, path,{resultType:"PATH"})
return maps.map(function(item,index) {
return eval( '(' + item.replace(/\$/,"obj") + ')' );
})
}
var jsonObject = [{
"type": "test",
"id": "100",
"values": {
"name": "Alpha"
},
"validations": []
}, {
"type": "services",
"validations": [{
"id": "200",
"name": "John",
"selection": [{
"id": "300",
"values": {
"name": "Blob"
}
}]
}]
}]
// this XPath will read all the id properties starting from the root element
console.log( "jsonPathRead All Ids" + JSON.stringify(jsonPathRead(jsonObject,"$..id"), null, 2) )
function getIds(obj) {
for (var x in obj) {
if (typeof obj[x] === 'object') {
getIds(obj[x]);
} else if (x === 'id') {
console.log(obj.id);
}
}
}

How to read array inside JSON Object this is inside another array

I am newbie to JSON, I am parsing a JSON Object and i was struck at a point where i have to read the array Elements inside a Object, that is again in another array..
Here is MY JSON
{
"DefinitionSource": "test",
"RelatedTopics": [
{
"Result": "",
"Icon": {
"URL": "https://duckduckgo.com/i/a5e4a93a.jpg"
},
"FirstURL": "xyz",
"Text": "sample."
},
{
"Result": "",
"Icon": {
"URL": "xyz"
},
"FirstURL": "xyz",
"Text": "sample."
},
{
"Topics": [
{
"Result": "",
"Icon": {
"URL": "https://duckduckgo.com/i/10d02dbf.jpg"
},
"FirstURL": "https://duckduckgo.com/Snake_Indians",
"Text": "sample"
},
{
"Result": "sample",
"Icon": {
"URL": "https://duckduckgo.com/i/1b0e4eb5.jpg"
},
"FirstURL": "www.google.com",
"Text": "xyz."
}
]
}
]
}
Here I need to read URL ,FIRSTURL and Text from RelatedTopics array and Topics array..
Can anyone help me. Thanks in advance.
Something like this
function (json) {
json.RelatedTopics.forEach(function (element) {
var url = element.Icon ? element.Icon.URL : 'no defined';
var firstURL = element.FirstURL ? element.FirstURL : 'no defined';
var text = element.Text ? element.Text : 'no defined';
alert("URL: " + url + "\nFirstURL: " + firstURL + "\nText: " + text);
if (element.Topics)
{
element.Topics.forEach(function (topicElement) {
alert("Topics - \n" + "URL: " + topicElement.Icon.URL + "\nFirstURL: " + topicElement.FirstURL + "\nText: " + topicElement.Text);
});
}
});
};
Look fiddle example
Loop through json Array like,
for(var i=0; i< RelatedTopics.length;i++){
if($.isArray(RelatedTopics[i])){
for(var j=0; j< RelatedTopics[i].Topics.length;j++){
var topics=RelatedTopics[i].Topics[j];
var text = topics.Text;
var firsturl = topics.Firsturl;
var url = topics.Icon.url;
}
}
}
if you want push it an array variable

Categories

Resources