Given an array of objects, what is the best way to find the object with a particular key in JS?
Using jQuery and underscoreJS is fine. I'm just looking for the easiest / least code answer.
Example:
An array of objects, where each object has a "name". Find the object with a particular "name".
var people = [{name: "A"}, {name: "B"}, {name: "C"}];
My current solution:
Pass in the array, the key (e.g. "name"), and the value (e.g. "C").
function getObject(myArray, searchKey, searchValue) {
myArray.forEach(function(element){
if (element[searchKey] == searchValue) {
return element;
}
});
}
You can use Underscore.js's _.where function, like this
console.log(_.where(people, {
"name": "C"
}));
# [ { name: 'C' } ]
This returns all the objects in the array, which exactly matches the object we pass as the second argument.
You should go with grep of jQuery
var people = [{name: "A"}, {name: "B"}, {name: "C"}];
var obj1= jQuery.grep(people,function(n,i){return (n.name=='A')})
Using _.filter:
var people = [{name: "A"}, {name: "B"}, {name: "C"}];
var filteredPeople = _.filter(people, function(obj){
return obj['name'] == 'C';
});
console.log(JSON.stringify(filteredPeople)) // [{"name":"C"}]
If you want an array without the matched object(s), use _.reject:
var filteredPeople = _.reject(people, function(obj){
return obj['name'] == 'C';
});
console.log(JSON.stringify(filteredPeople)) // [{name: "A"}, {name: "B"}]
Without any custom library you can also do this. Please take a look at the following code
var people = [{name: "A"}, {name: "B"}, {name: "C"}],
match=function(element){
return element.name==="A";
};
console.log(people.filter(match));
But it is kind of static code . I don't know how to pass the key and value to the match() function.
I'm surprised not to find the obvious answer here, so: To do that with Underscore, you'd use _.find:
function getObject(myArray, searchKey, searchValue) {
return _.find(myArray, function(entry) { return entry[seachKey] === searchValue; });
}
You don't need Underscore for this, though; JavaScript's array type has find (as of ES5):
function getObject(myArray, searchKey, searchValue) {
return myArray.find(function(entry) { return entry[searchKey] === searchValue; });
}
As of ES2015+, you can make it more concise with an arrow function and destructuring:
function getObject(myArray, searchKey, searchValue) {
return myArray.find(({[searchKey]: value}) => value === searchValue);
}
Live example of that last one:
function getObject(myArray, searchKey, searchValue) {
return myArray.find(({[searchKey]: value}) => value === searchValue);
}
const people = [{name: "A"}, {name: "B"}, {name: "C"}];
console.log(getObject(people, "name", "C"));
Related
I have an object that I need to filter against and return a new object. The goal is to get all ids that contain "A" in val, BUT only include ids with a unique val.
Below is what I'm currently doing, but I wonder if there's a more efficient way to do this. As can be seen when you run the code snippet, the new object should look like this:
{
"id1": {
"val": "AAA"
},
"id4": {
"val": "ABC"
}
}
const obj = {
id1: {
val: 'AAA',
},
id2: {
val: 'BBB',
},
id3: {
val: 'AAA',
},
id4: {
val: 'ABC',
},
};
// Filtered object
const obj2 = {};
let matched = '';
for (const key in obj) {
if (matched.indexOf(obj[key].val) < 0 && obj[key].val.indexOf('A') > -1) {
obj2[key] = obj[key];
matched += obj[key].val + ' ';
}
}
console.log(obj2);
Instead of building up a string for matched, you should use a Set (O(1) string comparisons for each operation instead of searching an increasingly long string in time proportional to the length of that string – and not running into issues with keys that contain spaces). includes is also a nice modern alternative to indexOf(…) > -1, although not faster.
Also, when using objects to store key/value mappings, you should use prototypeless objects – starting from Object.create(null) – to avoid setter weirdness (mostly __proto__) and tempting but fragile methods (name collisions with Object.prototype), and as a matter of good practice even when that isn’t a concern. Or just use Maps instead.
const obj = {
id1: {val: 'AAA'},
id2: {val: 'BBB'},
id3: {val: 'AAA'},
id4: {val: 'ABC'},
};
// Filtered object
const obj2 = Object.create(null);
const matched = new Set();
for (const key in obj) {
if (!matched.has(obj[key].val) && obj[key].val.includes('A')) {
obj2[key] = obj[key];
matched.add(obj[key].val);
}
}
console.log(obj2);
explain the use of the arrow function inside the .map() method here.
let mFunc= function(fname, data) {
for (i in data.shop) { //iterating through the JSON data
if (data.shop[i].name == fname) {
let dataSv = data.shop[i];
// We found a match, display details
for (y in dataSv){
if (typeof dataSv[y][0] === 'object') {
dataSv[y] = dataSv[y].map(z => z.name) // explain me this part
}
alert(i + " : " + dataSv[y])
}
}
}
}
}
.map(z => z.name)
Is shorthand for:
.map(z => {
return z.name;
})
So when you are only going to write a single line inside your function, and that is a return statement, you can use this shorthand.
This is just converting array of objects to array of strings which will contain the name of each element.
If you write an expression after => in array function with it will return that expression.
Body of arrow function
Arrow functions can have either a concise body or the usual block body.
In a concise body, only an expression is specified, which becomes the implicit return value. In a block body, you must use an explicit return statement
dataSv[y] = dataSv[y].map(z => z.name)
Is equivalent to
dataSv[y] = dataSv[y].map(z => {
return z.name;
})
Check this example. It may make it clearer
dataSv = [
[
{name: 'one', id: 1},
{name: 'two', id: 2},
{name: 'three', id: 3}
],
[
{name: 'eleven', id: 11},
{name: 'twelve', id: 12},
{name: 'thirteen', id: 13}
],
[
{name: 'twenty-one', id: 21},
{name: 'twenty-two', id: 22},
{name: 'twenty-three', id: 23}
]
]
dataSv[0] = dataSv[0].map(z => z.name)
dataSv[1] = dataSv[1].map(z => z.name)
dataSv[2] = dataSv[2].map(z => z.name)
console.info(dataSv)
It will return simply the "name" property of each element of the array, instead of the original object.
Basically whatever the code on the right-hand side of the => evaluates to becomes the returned value for each array element that .map() iterates over.
See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map for more info and a runnable demo you can play with
I have an object that looks something like this
{id: "2", name: "foo", price: "1"}
I want to transform this to the following
{2: {id: "2", name: "foo", price: "1"}}
I'm able to achieve this today if I wrap this object with a simple array like so thanks to the friendly keyBy method for array
_.keyBy([action.response], function(item) { return item.id; });
What I would prefer of course is the same result but without having to wrap this with an array first. Does transform/reduce or some other lodash v4 method provide this functionality?
Short solution based on ES6 computed property names: { [obj.id]: obj }
Example:
var obj = {id: "2", name: "foo", price: "1"}
var transformed = { [obj.id]: obj }
console.log(transformed)
You can do this directly with a function:
function convert(obj) {
var result = {}
result[obj.id] = obj
return result
}
Is that what you are looking for?
I have a data that looks of the format
[
{name: "a", class: "AA"},
{name: "b", class: "BB"},
{name: "xx", class: "XY"},
{name: "a", class: "AA"},
{name: "b", class: "BB"}
];
I am trying to reduce this to count the occurrences of combination of unique name and class value as follows
name:"a", class:"AA", count: 2,
name:"b", class: "BB", count: 2,
name:"xx", class:"XY", count:1
How can I achieve this in Javascript reducing elements of one variable is something I am familiar with but the combination of two different variable is where I need help. Sorry if this seems to basic for some of you.
Thank you
One way of doing it is to iterate over the data array and concatenate the name and class properties of each object, then use that as a key for a counter object, incrementing the value of each key.
var data = [{name:"a", class: "AA"}, {name:"b", class: "BB"}, {name:"xx", class: "XY"}, {name:"a", class:"AA"}, {name: "b", class:"BB"}];
var counter = {};
for (let obj of data) {
let key = `${obj.name}${obj.class}`;
if (counter[key] === undefined) {
counter[key] = 0;
}
counter[key]++;
}
console.log(counter);
Please keep in mind that the sample code uses features from es6
You can do this with forEach loop and optional thisArg parameter.
var data = [{name:"a", class: "AA"}, {name:"b", class: "BB"}, {name:"xx", class: "XY"}, {name:"a", class:"AA"}, {name: "b", class:"BB"}];
var result = [];
data.forEach(function(e) {
if(!this[e.name+'|'+e.class]) {
this[e.name+'|'+e.class] = {name: e.name, class: e.class, count: 0}
result.push(this[e.name+'|'+e.class]);
}
this[e.name+'|'+e.class].count++;
});
console.log(result);
You can do this way:
var input = [
{name: "a", class: "AA"},
{name: "b", class: "BB"},
{name: "xx", class: "XY"},
{name: "a", class: "AA"},
{name: "b", class: "BB"}
];
var output = {};
input.forEach(function(element) {
output[element.name] = output[element.name] || {};
output[element.name][element.class] = output[element.name][element.class] || 0;
++output[element.name][element.class];
});
console.log(output);
Provided that your objects are all ordered like in your example you can use JSON.stringify() to convert them to a string. Use those strings as key for an object and use the object to count the appearances of each key.
Advantages: Works for every object, regardless of the properties
Disadvantages: You have to make sure the properties are all in the same order
Here's a JsFiddle:
https://jsfiddle.net/jgn2matg/
data = [] // your array here
data.reduce((result, value) => {
const resultValue = result.find(resultItem => resultItem.name === value.name && resultItem.class === value.class);
resultValue ? resultValue.count++ : result.push(Object.assign(value, {count: 1}));
return result;
}, []);
I have a Backbone collection, where I am iterating through and looking up mdl.get('group'). This returns:
[undefined, undefined, group, group, group, undefined, group, group, group, undefined, group, group]
What I would like it to return is arrays (or collections) of those group models:
[undefined, undefined, [group], undefined, [group], undefined, [group]
I am trying to think of the best way to:
iterate through the object
return 'undefined' values without changing them
find siblings with similar values
collapse them into an array, within the object
Much like the _.groupBy method, but I need to retain the correct order of the models within the object.
I would love some ideas on how to approach this. So far I'm stumbling on how to tackle this one correctly.
thanks!
After some tinkering, came up with this solution. I extended Array.prototype so that you can easily drop it in. You can make an Underscore extension or a Collection.prototype method out of it:
Array.prototype.pack = function(field) {
var result = [], target, lastItem;
while(this.length > 0) {
var item = this.shift();
if(item === undefined) {
target = result;
} else {
if(!lastItem || item[field] != lastItem[field]) {
target = [];
result.push(target);
}
}
target.push(item);
lastItem = item;
}
return result;
}
Note it wasn't heavy tested, and for sure it can be refined, but should give you an idea.
You can use it like this:
models.pack("name")
where models is a plain array like [undefined, {field:value}].
Tried to be as concise as possible. A working demo here: http://jsfiddle.net/YZQ6v/
You can use collection.groupBy() to do this. First groupBy "group" to create a hash of grouped models then use collection.map to map the siblings onto the models.
Here is a fiddle:
http://jsfiddle.net/puleos/w7drB/
var models = [
{name: "Scott", group: "alpha"},
{name: "Rose", group: "alpha"},
{name: "Charles", group: "alpha"},
{name: "Stan"},
{name: "John"},
{name: "Mary", group: "beta"},
{name: "Dan", group: "beta"},
{name: "Paul", group: "beta"},
{name: "Grace"},
{name: "Sarah", group: "omega"}
];
var SourceCollection = Backbone.Collection.extend({});
var sourceCollection = new SourceCollection(models);
var grouped = sourceCollection.groupBy('group');
sourceCollection.map(function(model) {
if(model.has("group")) {
model.set({siblings: grouped[model.get("group")]});
}
});
// sourceCollection with siblings
console.log(sourceCollection);