I am having a complex JSON object which I want to compare like below :
$scope.new = [
{
"name": "Node-1",
"isParent": true,
"text" : [
{
"str" : "This is my first Node-1 string",
"parent":[]
},
{
"str" : "This is my second Node-1 string",
"parent":[]
}],
"nodes": [
{
"name": "Node-1-1",
"isParent": false,
"text" : [
{
"str" : "This is my first Node-1-1 string",
"parent":[]
},
{
"str" : "This is my second Node-1-1 string",
"parent":[]
}],
"nodes": [
{
"name": "Node-1-1-1",
"isParent": false,
"text" : [
{
"str" : "This is my first Node-1-1-1 string",
"parent":[]
},
{
"str" : "This is my second Node-1-1-1 string",
"parent":[]
}],
"nodes": []
}
]
}
]
}
]
But while comparing I want to ignore 1 property also but as I am using Angular.js I don't see any option in angular.equal which will omit that property while comparing 2 object.
console.log(angular.equals($scope.new,$scope.copy));
So while doing research I came with below answer which is using lodash having emit option but problem is I guess omit create a copy and I guess I will have performance degradation in case of lodash.
Exclude some properties in comparison using isEqual() of lodash
So now I am thinking to convert object so string and then do comparison and I guess that will be fast but problem is how I will omit that property while string comparison?
Something like this:
var str1 = JSON.stringify(JSON.stringify($scope.new));
var str2 = JSON.stringify(JSON.stringify($scope.copy));
console.log(str1==str2);
Note: I want to ignore isParent property while comparing 2 object.
What would be the best way to do compare 2 object?
Converting to strings is not the best approach in these cases.
Keep them as objects.
Using loadash:
const propertiesToExclude = ['isParent'];
let result = _.isEqual(
_.omit(obj1, propertiesToExclude),
_.omit(obj2, propertiesToExclude)
);
Using plain AngularJS, create a copy of the objects removing the not needed properties and then compare them:
let firstObj = angular.copy(obj1);
let secondObj = angular.copy(obj2);
const propertiesToExclude = ['isParent'];
function removeNotComparatedProperties(obj) {
propertiesToExclude.forEach(prop => {
delete obj[prop];
});
}
removeNotComparatedProperties(firstObj);
removeNotComparatedProperties(secondObj);
angular.equals(firstObj, secondObj);
You can use lodash and override the standard comparator used for deep comparison if you use _.isEqualWith:
var objA = {
isParent: true,
foo: {
isParent: false,
bar: "foobar"
}
};
var objB = {
isParent: false,
foo: {
isParent: true,
bar: "foobar"
}
};
var comparator = function(left, right, key) {
if (key === 'isParent') return true; // if the key is 'isParent', mark the values equal
else return undefined; // else fall back to the default behavior
}
var isEqual = _.isEqualWith(objA, objB, comparator);
console.log(isEqual); // true
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
To exclude multiple properties, extend the comparator function accordingly:
var comparator = function(left, right, key) {
if (key === 'isParent' || key === 'anotherKey') return true;
else return undefined;
}
You could also use a number of different approaches syntactically, depending on what you prefer -- a switch statement, an array that you iterate...
Related
Is there anyways to pass the condition for if statement dynamically along with its operator without using eval() or new function() that affects it's performance.
<script>
var Students = [{
"name": "Raj",
"Age":"15",
"RollNumber": "123",
"Marks": "99",
}, {
"name": "Aman",
"Age":"14",
"RollNumber": "223",
"Marks": "69",
},
{
"name": "Vivek",
"Age":"13",
"RollNumber": "253",
"Marks": "89",
}
];
*Number of condition are dynamic. can even contain 3 or 4 condition at a time*
var condition = [{"attribute":"el.Age","operator":">=","value":14},
{"attribute":"el.Marks","operator":"<=","value":70}];
var newArray =Students.filter(function (el)
{
if( condition[0].attribute condition[0].operator condition[0].value && condition[1].attribute condition[1].operator condition[1].value ){
return true;
});
console.log(newArray);
</script>
Yes, you pass the operands and the operator into a function that dispatches based on the operator to something that does the operation.
As a code sketch:
const operators = new Map([
["<", lt],
[">", gt],
["<=", lte],
[">=", gte],
// ...
]);
function evaluate(operator, leftOperand, rightOperand) {
const evaluator = operators.get(operator);
if (!evaluator) {
throw new Error(`Unknown operator ${operator}`);
}
return evaluator(leftOperand, rightOperand);
}
...where lt, gt, etc. are functions that perform the operation. I've used operator, leftOperand, and rightOperand above, but you could use attribute and value if you prefer. You'll need something like this to get the value of the "attribute" (the JavaScript term is "property").
(Or you could do the operations in the evaluate function itself in a big switch.)
Then you use that function in the filter:
let newArray = Students.filter(student => conditions.every(({operator, attribute, value}) => {
const leftOperand = getProperty(student, attribute);
return evaluate(operator, leftOperand, value);
}));
I've used every there because you're using an && condition. (Note I renamed the condition array to conditions since arrays hold more than one value.)
I would like to apply a function to all methods in a JSON object which have the a name matching a condition in my case "filename". The problem is that that filename is a nested property in the JSON object it is usually just nested 1 level but could potentially be more. I have considered just async.each though the object and just see if that object has the property i want and run my function but that does not seem efficient.
An example object is
"hello"{
"name": "Distribution Board 5",
"Filename": "helloworld.png",
"id": "5",
"location": "somewhere",
"description": "something",
}
"test"{
"testproperty": 123
"anothertest": 456
}
"extra": [
{
"Filename": "image.png",
"Tag": "It's just a square",
"Deleted": false
}
]
Would you be able to suggest an efficient implementation to the problem
const object = {
hello: {
name: 'Distribution Board 5',
Filename: 'helloworld.png',
id: '5',
location: 'somewhere',
description: 'something'
},
test: {
testproperty: 123,
anothertest: 456,
nestedproperty: {
mismatching: 'key',
Filename: 'test.jpg'
}
},
extra: [{
Filename: 'image.png',
Tag: 'It\'s just a square',
Deleted: false
}]
}
function walk(o, fn, key) {
switch (typeof o) {
case 'object':
if (o) {
Object.entries(o).forEach(
([key, value]) => walk(value, fn, key)
)
break
}
default:
fn(key, o)
break
}
}
walk(object, (key, value) => {
if (key === 'Filename') console.log(`Found ${value}`)
})
Here's a walk() function that works on JSON-serializable objects. The callback function fn can perform the logic you need.
I don't think async.each is needed here. It could be done with a little code like this:
const run = (obj) => Object.keys(obj).find(
x => x === "Filename"
|| typeof obj[x] === "object"
&& obj[x]
&& run(obj[x])
);
run(YourObjectHere)
It'll simply go recursively though all your keys and check if one of them is "Filename".
Probably this could be improved with usage of Object.keys(x).includes but it would get more complex do all depends on how many objects your set contains.
I have this kind of array:
var foo = [ { "a" : "1" }, { "b" : "2" }, { "a" : "1" } ];
I'd like to filter it to have:
var bar = [ { "a" : "1" }, { "b" : "2" }];
I tried using _.uniq, but I guess because { "a" : "1" } is not equal to itself, it doesn't work. Is there any way to provide underscore uniq with an overriden equals function?
.uniq/.unique accepts a callback
var list = [{a:1,b:5},{a:1,c:5},{a:2},{a:3},{a:4},{a:3},{a:2}];
var uniqueList = _.uniq(list, function(item, key, a) {
return item.a;
});
// uniqueList = [Object {a=1, b=5}, Object {a=2}, Object {a=3}, Object {a=4}]
Notes:
Callback return value used for comparison
First comparison object with unique return value used as unique
underscorejs.org demonstrates no callback usage
lodash.com shows usage
Another example :
using the callback to extract car makes, colors from a list
If you're looking to remove duplicates based on an id you could do something like this:
var res = [
{id: 1, content: 'heeey'},
{id: 2, content: 'woah'},
{id: 1, content:'foo'},
{id: 1, content: 'heeey'},
];
var uniques = _.map(_.groupBy(res,function(doc){
return doc.id;
}),function(grouped){
return grouped[0];
});
//uniques
//[{id: 1, content: 'heeey'},{id: 2, content: 'woah'}]
Implementation of Shiplu's answer.
var foo = [ { "a" : "1" }, { "b" : "2" }, { "a" : "1" } ];
var x = _.uniq( _.collect( foo, function( x ){
return JSON.stringify( x );
}));
console.log( x ); // returns [ { "a" : "1" }, { "b" : "2" } ]
When I have an attribute id, this is my preffered way in underscore:
var x = [{i:2}, {i:2, x:42}, {i:4}, {i:3}];
_.chain(x).indexBy("i").values().value();
// > [{i:2, x:42}, {i:4}, {i:3}]
Using underscore unique lib following is working for me, I m making list unique on the based of _id then returning String value of _id:
var uniqueEntities = _.uniq(entities, function (item, key, a) {
return item._id.toString();
});
Here is a simple solution, which uses a deep object comparison to check for duplicates (without resorting to converting to JSON, which is inefficient and hacky)
var newArr = _.filter(oldArr, function (element, index) {
// tests if the element has a duplicate in the rest of the array
for(index += 1; index < oldArr.length; index += 1) {
if (_.isEqual(element, oldArr[index])) {
return false;
}
}
return true;
});
It filters out all elements if they have a duplicate later in the array - such that the last duplicate element is kept.
The testing for a duplicate uses _.isEqual which performs an optimised deep comparison between the two objects see the underscore isEqual documentation for more info.
edit: updated to use _.filter which is a cleaner approach
The lodash 4.6.1 docs have this as an example for object key equality:
_.uniqWith(objects, _.isEqual);
https://lodash.com/docs#uniqWith
Try iterator function
For example you can return first element
x = [['a',1],['b',2],['a',1]]
_.uniq(x,false,function(i){
return i[0] //'a','b'
})
=> [['a',1],['b',2]]
here's my solution (coffeescript) :
_.mixin
deepUniq: (coll) ->
result = []
remove_first_el_duplicates = (coll2) ->
rest = _.rest(coll2)
first = _.first(coll2)
result.push first
equalsFirst = (el) -> _.isEqual(el,first)
newColl = _.reject rest, equalsFirst
unless _.isEmpty newColl
remove_first_el_duplicates newColl
remove_first_el_duplicates(coll)
result
example:
_.deepUniq([ {a:1,b:12}, [ 2, 1, 2, 1 ], [ 1, 2, 1, 2 ],[ 2, 1, 2, 1 ], {a:1,b:12} ])
//=> [ { a: 1, b: 12 }, [ 2, 1, 2, 1 ], [ 1, 2, 1, 2 ] ]
with underscore i had to use String() in the iteratee function
function isUniq(item) {
return String(item.user);
}
var myUniqArray = _.uniq(myArray, isUniq);
I wanted to solve this simple solution in a straightforward way of writing, with a little bit of a pain of computational expenses... but isn't it a trivial solution with a minimum variable definition, is it?
function uniq(ArrayObjects){
var out = []
ArrayObjects.map(obj => {
if(_.every(out, outobj => !_.isEqual(obj, outobj))) out.push(obj)
})
return out
}
var foo = [ { "a" : "1" }, { "b" : "2" }, { "a" : "1" } ];
var bar = _.map(_.groupBy(foo, function (f) {
return JSON.stringify(f);
}), function (gr) {
return gr[0];
}
);
Lets break this down. First lets group the array items by their stringified value
var grouped = _.groupBy(foo, function (f) {
return JSON.stringify(f);
});
grouped looks like:
{
'{ "a" : "1" }' = [ { "a" : "1" } { "a" : "1" } ],
'{ "b" : "2" }' = [ { "b" : "2" } ]
}
Then lets grab the first element from each group
var bar = _.map(grouped, function(gr)
return gr[0];
});
bar looks like:
[ { "a" : "1" }, { "b" : "2" } ]
Put it all together:
var foo = [ { "a" : "1" }, { "b" : "2" }, { "a" : "1" } ];
var bar = _.map(_.groupBy(foo, function (f) {
return JSON.stringify(f);
}), function (gr) {
return gr[0];
}
);
You can do it in a shorthand as:
_.uniq(foo, 'a')
I am Having the Array of objects. Like this
var result=[{"batchId":123, "licenseId":2345ef34, "name":"xxx"},
{"batchId":345, "licenseId":2345sdf334, "name":"www"},
{"batchId":145, "licenseId":234sdf5666, "name":"eee"},
{"batchId":455, "licenseId":asfd236645 },
{"batchId":678, "name":"aaa"}]
i want to have the array which is contains all the three properties. the Output should be like this.
[{"batchId":123, "licenseId":2345ef34, "name":"xxx"},
{"batchId":345, "licenseId":2345sdf334, "name":"www"},
{"batchId":145, "licenseId":234sdf5666, "name":"eee"}]
can anybody Help me on this
This is simple with the array .filter() method:
var result=[
{"batchId":123, "licenseId":"2345ef34", "name":"xxx"},
{"batchId":345, "licenseId":"2345sdf334", "name":"www"},
{"batchId":145, "licenseId":"234sdf5666", "name":"eee"},
{"batchId":455, "licenseId":"asfd236645" },
{"batchId":678, "name":"aaa"}
];
var filtered = result.filter(function(v) {
return "batchId" in v && "licenseId" in v && "name" in v;
});
console.log(filtered);
The function you pass to .filter() is called for each element in the array. Each element for which you return a truthy value will be included in the resulting array.
In the code above I simply test if all three of those specific properties are present, although there are other tests you could use that would get the same result for that data:
var result=[ {"batchId":123, "licenseId":"2345ef34", "name":"xxx"}, {"batchId":345, "licenseId":"2345sdf334", "name":"www"}, {"batchId":145, "licenseId":"234sdf5666", "name":"eee"}, {"batchId":455, "licenseId":"asfd236645" }, {"batchId":678, "name":"aaa"} ];
var filtered = result.filter(function(v) {
return Object.keys(v).length === 3;
});
console.log(filtered);
Note that you need to put your licenseId values in quotes, because they seem to be string values.
var result = [{
"batchId": 123,
"licenseId": '2345ef34',
"name": "xxx"
}, {
"batchId": 345,
"licenseId": '2345sdf334',
"name": "www"
}, {
"batchId": 145,
"licenseId": '234sdf5666',
"name": "eee"
}, {
"batchId": 455,
"licenseId": 'asfd236645'
}, {
"batchId": 678,
"name": "aaa"
}];
function hasProperties(object) {
return object.hasOwnProperty('batchId') && object.hasOwnProperty('licenseId') && object.hasOwnProperty('name')
}
result.filter(e => hasProperties(e));
I'm wondering if something like this is possible - an extend that looks at an array of objects
Lets say I have :
myObj = [
{"name" : "one", "test" : ["1","2"]},
{"name" : "two", "test" : ["1","2"]}
]
And I'm trying to extend into that
{"name" : "one" , "test" : ["1","3"]}
So the result would be -
myObj = [
{"name" : "one", "test" : ["1","2", "3"]},
{"name" : "two", "test" : ["1","2"]}
]
And this would be like extend in that if there is no object there it would just create one. IS something like this possible? Thanks!
It looks like you want something like:
function deepSetMerge(base, next) {
var dataEntry = base.filter(function(it) {
return it.name === next.name;
})[0];
if (dataEntry) {
var diff = next.test.filter(function(it) {
return dataEntry.test.indexOf(it) === -1;
});
dataEntry.test = dataEntry.test.concat(diff).sort();
} else {
base.push(next);
}
}
var data = [{
"name": "one",
"test": ["1", "2"]
}, {
"name": "two",
"test": ["1", "2"]
}];
var add = {
"name": "one",
"test": ["1", "3"]
};
var obj = {
"name": "totally-unique",
"test": [1, 2, "nine", 17.0]
};
deepSetMerge(data, add);
deepSetMerge(data, obj);
document.getElementById('results').textContent = JSON.stringify(data);
<pre id="results"></pre>
This function works by finding the existing entry, by name, if one exists. If not, we just push the object to be merged in as a new entry.
If the entry does exist, find the difference between the two sets of items, then concatenate them onto the end of the existing array (and sort the results).
This is pretty specific to your data structure and not the most flexible solution in the world, but hopefully shows the algorithm well.
As a universal extend implementation this would be quite tricky and costly. It is why deep dives are usually avoided. It is better to know your data model and work with it that way.
Using your example I would establish some assumptions on the data model in question:
Our object will have a name property which is unique and can be searched for.
The value of the test property is an array (makes this easier but it could be an object that you use a typical extend implementation on.
In which case you could approach it like this:
var myObj = [
{name: 'one', test: ['1', '2']},
{name: 'two', test: ['1', '2']}
];
function extendTestValue(data, value) {
var names = data.map(function(item) {
return item.name;
});
var index = names.indexOf(value.name);
if (index < 0) {
data.push(value);
} else {
value.test.forEach(function(item) {
if (data[index].test.indexOf(item) < 0) {
data[index].test.push(item);
}
});
}
return data;
}