I have an obj, with an array of objects - something like below (I've not pasted it here because its HUGE). I'm trying to loop through the array of objects - deleting those objects from the array which contain a value. I have written the following code... (using lodash)
When looping over the array its randomly missing a few 'Foo's - so not all the Foo objects are deleted... even though they contain the key Foo. It does ignore that which doesn't contain Foo though.
obj = {
array : [
{
key1 : 'Foo',
Key2 : 'fi'
},
{
key1 : 'Foo',
Key2 : 'fi',
Key3 : 'blah'
},
{
key1 : 'Fred',
Key2 : 'fi'
},
{
key1 : 'Foo',
Key2 : 'fi'
}
... etc....
]
}
var items = obj.array
_.forEach(items, function(n, index) {
var isFoo = _.includes(n, 'Foo');
console.log(isFoo);
if (isFoo) {
items.splice(index, 1);
}
});
I suspect things are getting confused because you are changing the array at the same time as you are looping through it.
_.remove is probably a better option for you here:
var obj = {
array : [
{
key1 : 'Foo',
Key2 : 'fi'
},
{
key1 : 'Foo',
Key2 : 'fi',
Key3 : 'blah'
},
{
key1 : 'Fred',
Key2 : 'fi'
},
{
key1 : 'Foo',
Key2 : 'fi'
}
]
};
_.remove(obj.array, function(n, index) {
return _.includes(n, 'Foo');
});
console.dir(obj.array)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.0/lodash.js"></script>
I ditched lodash, and just did a reverse for-loop (my thinking was that as things were being removed, the index would change... and maybe that was causing the errors?)
var items = obj.array;
var i;
for (i = items.length - 1; i >= 0; i -= 1) {
if (items[i].type === 'Foo') {
items.splice(i, 1);
}
}
Related
I'm trying to compare two JSON structures by their keys, not the values inside the keys.
The goal is to see if the one is different by the other. I don't need a diff, just a simple flag. I've tried to do it but somehow I can't get a solution that would not be overcomplicated.
Sample JSON1 structure
{ "parentKey" :
{ "childKey" : { "nestedKey" : "value" },
"childKey2: "value2" }}
JSON2
{ "parentKey" :
{ "childKey" : { "nestedKey" : "value2",
"nestedKey2": "value3"},
"childKey2: "value2" }}
If you only need to see if the 2 objects are not exactly identical (just the keys, not the values) It's pretty simple, you just check Every key in both objects. If you need to know what key is different or how is different it's entirely a different problem.
let o1 = { "parentKey" :
{ "childKey" : { "nestedKey" : "value" },
"childKey2": "value2" }}
let o2 = { "parentKey" :
{ "childKey" : { "nestedKey" : "value2",
"nestedKey2": "value3"},
"childKey2": "value2" }}
let keysInObj1 = []
let keysInObj2 = []
function compare(object, arr){
for (const obj in object){
arr.push(obj)
if(typeof(object[obj]) == 'object'){
compare(object[obj], arr)
}
}
}
compare(o1, keysInObj1)
compare(o2, keysInObj2)
keysInObj1.forEach((val, index)=>{
if(keysInObj2[index] != val){
console.log('Objects are not the same')
}
})
JsFiddle
Here is an iterative solution using object-scan
This solution could easily be modified to tell you exactly the keys that the objects are different.
// const objectScan = require('object-scan');
const d1 = { parentKey: { childKey: { nestedKey: 'value' }, childKey2: 'value2' } };
const d2 = { parentKey: { childKey: { nestedKey: 'value2', nestedKey2: 'value3' }, childKey2: 'value2' } };
const cmp = (a, b) => {
const r1 = objectScan(['**'], { joined: true })(a);
const r2 = objectScan(['**'], { joined: true })(b);
if (r1.length !== r2.length) {
return false;
}
return r1.every((e) => r2.includes(e));
};
console.log(cmp(d1, d2));
// => false
console.log(cmp(d1, d1));
// => true
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#14.3.1"></script>
Disclaimer: I'm the author of object-scan
const compareKeys = (obj1, obj2) =>
Object.keys(obj1).length === Object.keys(obj2).length &&
Object.keys(obj1).every((key) => key in obj2);
var object1 = {
"banana": "bar",
"orange": "foo"
};
var object2 = {
"banana": "foo",
"orange": "bar"
};
var object3 = {
"aaaaa": "bar",
"jjjjj": "foo"
};
//Expected output: true
console.log(compareKeys(object1, object2));
//Expected output: false
console.log(compareKeys(object1, object3));
I have tried this approach keep in mind the structure should be same too.
Therefore,
{name: "Anuj", isTaxPayer: true}
{isTaxPayer: true, name: "Shonster"}
will not be same
var keys1 = [];
var keys2 = [];
var json1 = { "parentKey" :
{ "childKey" : { "nestedKey" : "value" },
"childKey2": "value2" }};
var json2 = { "parentKey" :
{ "childKey" : { "nestedKey" : "value2",
"nestedKey2": "value3"},
"childKey2": "value2" }};
function getKeys(Obj, res){
res === undefined ? res = [] : 1;
for(prop in Obj){
res.push(prop);
if(Object.prototype.toString.call(Obj[prop]).match(/\s([a-zA-Z]+)/)[1] === "Object"){
getKeys(Obj[prop], res);
}
}
return res;
}
function isDifferent(){
keys1 = getKeys(json1).join("").toLowerCase();
keys2 = getKeys(json2).join("").toLowerCase();
keys1 === keys2 ? alert("SAME") : alert("NOPE");
}
isDifferent();
I have this object:
obj = {key : val,
key1 : val1,
key2 : {key21 : val21,key22 : val22},
key3 : val3}
I want to generate a new object to be like:
objnew = {key : val,
key1 : val1,
key21 : val21,
key22 : val22,
key3 : val3}
As per your comments, if you really want to keep the order for whatever reason, and don't want to do it for anything but key2, here's a possible solution.
Please read this question for information about order of object keys. In short, it's most likely a bad idea to rely on it in most cases. You'd be better off using a Map instance or just an array.
let obj = {
key: 0,
key1: 1,
key2: {key21: 21, key22: 22},
key3: 3
};
let objArray = Object.keys(obj).map(key => ({key, value: obj[key]}));
let result = objArray.reduce((result, entry) =>
Object.assign(result, entry.key === 'key2' ? entry.value : {[entry.key]: entry.value})
, {});
console.log(result);
For 2-level object structure (with Object.keys() and Object.assign() functions):
var obj = {
'key' : 'val',
'key1' : 'val1',
'key2' : {'key21' : 'val21', 'key22' : 'val22'},
'key3' : 'val3'
};
Object.keys(obj).forEach(function(k){
if (typeof obj[k] === 'object') {
Object.assign(obj, obj[k]);
delete obj[k];
}
});
console.log(obj);
Here you go:
var obj = {
key : 1,
key1 : 2,
key2 : { key21 : 3,key22 : 4 },
key3 : 5
};
Object.keys(obj).forEach(function(key) {
if(typeof obj[key] === 'object') {
Object.keys(obj[key]).forEach(function(innerKey) {
obj[innerKey] = obj[key][innerKey];
});
delete obj[key];
}
});
console.log(obj);
I have object and this object include objects too. It looks like:
$scope.data = {
tree : {
name : 'oak',
old : 54
},
dog : {
name : 'Lucky',
old : 3
},
system1 : {
name : '',
old : ''
},
baby : {
name : 'Jack',
old : 1
},
cat : {
name : 'Fluffy',
old : 2
},
system2 : {
name : '-',
old : '-'
}
}
As you can see this objects has obj name like - tree, dog, system etc. And I want to take only objects with name system, but this name can changes like system1, system123, system8. So I try to use this reg exp for ignore numbers
replace(/\d+/g, '')
But I can't reach this object name. I try this:
angular.forEach($scope.data, function(item){conole.log(item)}) // but it shows content in obj not obj name..
How can I reach this obj name and distinguish this 2 system objects?
var data = {
tree : {
name : 'oak',
old : 54
},
dog : {
name : 'Lucky',
old : 3
},
system1 : {
name : '',
old : ''
},
baby : {
name : 'Jack',
old : 1
},
cat : {
name : 'Fluffy',
old : 2
},
system2 : {
name : '-',
old : '-'
}
}
data = Object.keys(data) // get keys
.filter(key => key.startsWith('system')) // filter keys starting with system
.map(key => data[key]) // map to the values, returning a new array
console.log(data) // and you have you array with systems
Pass another param to the function like key to the forEach callBack Function. It is the key of the each object inside the object in your use-case.
Check the below example
var items = {
car: {
a: 123
},
dog: {
b: 234
},
system: {
c: 456
}
};
angular.forEach(items, function(item, key) {
console.log(key);
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
You can use Object.keys(myObject), that return an array of all the keys of the passed object, for istance:
var myObject= {
cat : {
name : 'Fluffy',
old : 2
},
system2 : {
name : '-',
old : '-'
}
}
var keys = Object.keys(myObject); // Keys will be ['cat', 'system2']
Cheers
You have to pass another parameter to angular foreach function to get the key name of the object like this:-
angular.forEach($scope.data, function(item, key){ // Here key is the keyname of the object
console.log(item, key);
});
Example:
var array1 = [ {'key':1, 'property1': 'x'}, {'key':2, 'property1': 'y'} ]
var array2 = [ {'key':2, 'property2': 'a'}, {'key':1, 'property2': 'b'} ]
I want merge(array1, array2) to give me:
[
{'key':1, 'property1': 'x', 'property2' : 'b'},
{'key':2, 'property1': 'y', 'property2' : 'a'}
]
Is there an easy way to do this?
EDIT: several people have answered without looking too closely at my problem, please be note that I want to match similar objects in each array and combine their properties into my final array. Keys are unique and there will only ever be at most one object with a particular key in each array.
I wrote a quick not-so-quick solution. The one problem you might want to consider is whether a property from an object in the second array should override the same property, if present, in the second object it's being compared to.
Solution 1
This solution is of complexity O(n²). Solution 2 is much faster; this solution is only for those who don't want to be Sanic the Hedgehog fast.
JavaScript
var mergeByKey = function (arr1, arr2, key) {
// key is the key that the function merges based on
arr1.forEach(function (d, i) {
var prop = d[key];
// since keys are unique, compare based on this key's value
arr2.forEach(function (f) {
if (prop == f[key]) { // if true, the objects share keys
for (var x in f) { // loop through each key in the 2nd object
if (!(x in d)) // if the key is not in the 1st object
arr1[i][x] = f[x]; // add it to the first object
// this is the part you might want to change for matching properties
// which object overrides the other?
}
}
})
})
return arr1;
}
Test Case
var arr = [ {'key':1, 'property1': 'x'},
{'key':2, 'property1': 'y'} ],
arr2= [ {'key':2, 'property2': 'a'},
{'key':1, 'property2': 'b'} ];
console.log(mergeByKey(arr, arr2, "key"));
Results
/* returns:
Object
key: 1
property1: "x"
property2: "b"
__proto__: Object
and
Object
key: 2
property1: "y"
property2: "a"
__proto__: Object
*/
fiddle
Solution 2
As Vivin Paliath pointed out in the comments below, my first solution was of O(n²) complexity (read: bad). His answer is very good and provides a solution with a complexity of O(m + n), where m is the size of the first array and n of the second array. In other words, of complexity O(2n).
However, his solution does not address objects within objects. To solve this, I used recursion—read: the devil, just like O(n²).
JavaScript
var mergeByKey = function (arr1, arr2, key) {
var holder = [],
storedKeys = {},
i = 0; j = 0; l1 = arr1.length, l2 = arr2.length;
var merge = function (obj, ref) {
for (var x in obj) {
if (!(x in ref || x instanceof Object)) {
ref[x] = obj[x];
} else {
merge(obj[x], ref[x]);
}
}
storedKeys[obj.key] = ref;
}
for (; i < l1; i++) {
merge(arr1[i], storedKeys[arr1[i].key] || {});
}
for (; j < l2; j++) {
merge(arr2[j], storedKeys[arr2[j].key] || {});
}
delete storedKeys[undefined];
for (var obj in storedKeys)
holder.push(storedKeys[obj]);
return holder;
}
Test Case
var arr1 = [
{
"key" : 1,
"prop1" : "x",
"test" : {
"one": 1,
"test2": {
"maybe" : false,
"test3": { "nothing" : true }
}
}
},
{
"key" : 2,
"prop1": "y",
"test" : { "one": 1 }
}],
arr2 = [
{
"key" : 1,
"prop2" : "y",
"test" : { "two" : 2 }
},
{
"key" : 2,
"prop2" : "z",
"test" : { "two": 2 }
}];
console.log(mergeByKey(arr1, arr2, "key"));
Results
/*
Object
key: 1
prop1: "x"
prop2: "y"
test: Object
one: 1
test2: Object
maybe: false
test3: Object
nothing: true
__proto__: Object
__proto__: Object
two: 2
__proto__: Object
__proto__: Object
Object
key: 2
prop1: "y"
prop2: "z"
test: Object
one: 1
two: 2
__proto__: Object
__proto__: Object
*/
This correctly merges the objects, along with all child objects. This solutions assumes that objects with matching keys have the same hierarchies. It also does not handle the merging of two arrays.
fiddle
You could do something like this:
function merge(array1, array2) {
var keyedResult = {};
function _merge(element) {
if(!keyedResult[element.key]) {
keyedResult[element.key] = {};
}
var entry = keyedResult[element.key];
for(var property in element) if(element.hasOwnProperty(property)) {
if(property !== "key") {
entry[property] = element[property];
}
}
entry["key"] = element.key;
}
array1.forEach(_merge);
array2.forEach(_merge);
var result = [];
for(var key in keyedResult) if(keyedResult.hasOwnProperty(key)) {
result.push(keyedResult[key]);
}
return result.sort(function(a, b) {
return a.key - b.key;
});
}
You could eliminate the sort if you don't care about the order. Another option is to use an array instead of the map I have used (keyedResult) if you have numeric keys and don't care about the array being sparse (i.e., if the keys are non-consecutive numbers). Here the key would also be the index of the array.
This solution also runs in O(n).
fiddle
It would be preferable to use existing infrastructure such as Underscore's _.groupBy and _.extend to handle cases like this, rather than re-inventing the wheel.
function merge(array1, array2) {
// merge the arrays
// [ {'key':1, 'property1': 'x'}, {'key':2, 'property1': 'y'}, {'key':2, 'property2': 'a'}, {'key':1, 'property2': 'b'} ]
var merged_array = array1.concat(array2);
// Use _.groupBy to create an object indexed by key of relevant array entries
// {1: [{ }, { }], 2: [{ }, { }]}
var keyed_objects = _.groupBy(merged_array, 'key');
// for each entry in keyed_objects, merge objects
return Object.keys(keyed_objects).map(function(key) {
return _.extend.apply({}, keyed_objects[key]);
});
}
The idea here is using _.extend.apply to pass the array of objects grouped under a particular key as arguments to _.extend, which will merge them all into a single object.
I want to do something like this:
var data = [
{
sortData: {a: 'a', b: 2}
},
{
sortData: {a: 'a', b: 1}
},
{
sortData: {a: 'b', b: 5}
},
{
sortData: {a: 'a', b: 3}
}
];
data = _.sortBy(data, ["sortData.a", "sortData.b"]);
_.map(data, function(element) {console.log(element.sortData.a + " " + element.sortData.b);});
And have it output this:
"a 1"
"a 2"
"a 3"
"b 5"
Unfortunately, this doesn't work and the array remains sorted in its original form. This would work if the fields weren't nested inside the sortData. How can I use lodash/underscore to sort an array of objects by more than one nested field?
I've turned this into a lodash feature request: https://github.com/lodash/lodash/issues/581
Update: See the comments below, this is not a good solution in most cases.
Someone kindly answered in the issue I created. Here's his answer, inlined:
_.sortBy(data, function(item) {
return [item.sortData.a, item.sortData.b];
});
I didn't realize that you're allowed to return an array from that function. The documentation doesn't mention that.
If you need to specify the sort direction, you can use _.orderBy with the array of functions syntax from Lodash 4.x:
_.orderBy(data, [
function (item) { return item.sortData.a; },
function (item) { return item.sortData.b; }
], ["asc", "desc"]);
This will sort first ascending by property a, and for objects that have the same value for property a, will sort them descending by property b.
It works as expected when the a and b properties have different types.
Here is a jsbin example using this syntax.
There is a _.sortByAll method in lodash version 3:
https://github.com/lodash/lodash/blob/3.10.1/doc/README.md#_sortbyallcollection-iteratees
Lodash version 4, it has been unified:
https://lodash.com/docs#sortBy
Other option would be to sort values yourself:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
function compareValues(v1, v2) {
return (v1 > v2)
? 1
: (v1 < v2 ? -1 : 0);
};
var data = [
{ a: 2, b: 1 },
{ a: 2, b: 2 },
{ a: 1, b: 3 }
];
data.sort(function (x, y) {
var result = compareValues(x.a, y.a);
return result === 0
? compareValues(x.b, y.b)
: result;
});
// data after sort:
// [
// { a: 1, b: 3 },
// { a: 2, b: 1 },
// { a: 2, b: 2 }
// ];
The awesome, simple way is:
_.sortBy(data, [function(item) {
return item.sortData.a;
}, function(item) {
return item.sortData.b;
}]);
I found it from check the source code of lodash, it always check the function one by one.
Hope that help.
With ES6 easy syntax and lodash
sortBy(item.sortData, (item) => (-item.a), (item) => (-item.b))
I think this could work in most cases with underscore:
var properties = ["sortData.a", "sortData.b"];
data = _.sortBy(data, function (d) {
var predicate = '';
for (var i = 0; i < properties.length; i++)
{
predicate += (i == properties.length - 1
? 'd.' + properties[i]
: 'd.' + properties[i] + ' + ')
}
return eval(predicate)
});
It works and you can see it in Plunker
If the problem is an integer is converted to a string, add zeroes before the integer to make it have the same length as the longest in the collection:
var maxLength = _.reduce(data, function(result, item) {
var bString = _.toString(item.sortData.b);
return result > bString.length ? result : bString.length;
}, 0);
_.sortBy(data, function(item) {
var bString = _.toString(item.sortData.b);
if(maxLength > bString.length) {
bString = [new Array(maxLength - bString.length + 1).join('0'), bString].join('');
}
return [item.sortData.a, bString];
});
I've found a good way to sort array by multiple nested fields.
const array = [
{id: '1', name: 'test', properties: { prop1: 'prop', prop2: 'prop'}},
{id: '2', name: 'test2', properties: { prop1: 'prop second', prop2: 'prop second'}}
]
I suggest to use 'sorters' object which will describe a key and sort order. It's comfortable to use it with some data table.
const sorters = {
'id': 'asc',
'properties_prop1': 'desc',//I'm describing nested fields with '_' symbol
}
dataSorted = orderBy(array, Object.keys(sorters).map(sorter => {
return (row) => {
if (sorter.includes('_')) { //checking for nested field
const value = row["properties"][sorter.split('_')[1]];
return value || null;
};
return row[sorter] || null;// checking for empty values
};
}), Object.values(sorters));
This function will sort an array with multiple nested fields, for the first arguments it takes an array to modify, seconds one it's actually an array of functions, each function have argument that actually an object from 'array' and return a value or null for sorting. Last argument of this function is 'sorting orders', each 'order' links with functions array by index. How the function looks like simple example after mapping:
orderBy(array, [(row) => row[key] || null, (row) => row[key] || null , (row) => row[key] || null] , ['asc', 'desc', 'asc'])
P.S. This code can be improved, but I would like to keep it like this for better understanding.