So I have an interesting issue I am not sure how to follow, I need to use lodash to search two arrays in an object, looking to see if x already exists, lets look at a console out put:
There are two keys I am interested in: questChains and singleQuests, I want to write two seperate functions using lodash to say: find me id x in the array of objects where questChains questChainID is equal to x.
The second function would say: Find me a quest in the array of objects where singleQuests questTitle equals y
So if we give an example, you can see that questChainId is a 1 so if I pass in a 1 to said function I would get true back, I don't actually care about the object its self, else I would get false.
The same goes for singleQuests, If I pass in hello (case insensitive) I would get back true because there is a quest with the questTitle of 'Hello'. Again I don't care about the object coming back.
The way I would write this is something like:
_.find(theArray, function(questObject){
_.find(questObject.questChains, function(questChain){
if (questChain.questChainId === 1) {
return true;
}
});
});
This is just for the quest chain id comparison. This seems super messy, why? Because I am nesting lodash find, I am also nesting if. It gets a bit difficult to read.
Is this the only way to do this? or is there a better way?
Yeah it can be expressed more simply.
Try something like this:
var exampleArray = [{
questChains: [{
questChainId: 1,
name: 'foo'
}, {
questChainId: 2,
name: 'bar'
}],
singleQuests: [{
questTitle: 'hello world'
}]
}, {
questChains: [{
questChainId: 77,
name: 'kappa'
}]
}];
var result = _.chain(exampleArray)
.pluck('questChains')
.flatten()
.findWhere({ questChainId: 2 })
.value();
console.log('result', result);
<script src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/3.5.0/lodash.min.js"></script>
Using chain and value is optional. They just let you chain together multiple lodash methods more succinctly.
pluck grabs a property from each object in an array and returns a new array of those properties.
flatten takes a nested array structure and flattens it into flat array structure.
findWhere will return the first element which matches the property name/value provided.
Combining all of these results in us fetching all questChain arrays from exampleArray, flattening them into a single array which can be more easily iterated upon, and then performing a search for the desired value.
Case-insensitive matching will be slightly more challenging. You'd either need to either replace findWhere with a method which accepts a matching function (i.e. find) or sanitize your input ahead of time. Either way you're going to need to call toLower, toUpper, or some variant on your names to standardize your search.
Related
This question already has answers here:
Javascript: Using `.includes` to find if an array of objects contains a specific object
(7 answers)
Closed 28 days ago.
I am trying to understand the array "includes" function. My goal is to determine if an array includes a certain item. It works fine for an array of strings but when using objects it doesn't work.
var itemsString = ["name1", "name2"];
var itemsObject = [{ name: "name1" }, { name: "name2" }];
var itemToSearch = { name: "name1" };
console.log(itemsString.includes("name1" ));
console.log(itemsObject.includes(itemToSearch));
console.log(itemsObject.includes(x => x.name === "name1"));
Output:
true
false
false
Does "includes" work with objects or do I need to use another function?
You need to use Array.prototype.some() in this case. Array.prototype.includes() does not accept a function parameter*, and you're not testing for strict equality.
const itemsObject = [{ name: "name1" }, { name: "name2" }];
console.log(itemsObject.some(x => x.name === "name1"));
*Peer pressure from the comments section forces me to clarify that includes() does accept function parameters, but will not use the passed function as a predicate to determine whether a given item matches. Rather, it will try to find an item in the array that is strictly equal to the passed function.
In your last line you check wether a function is inside of your array. .includes also works for objects, but it compares them by reference. In your case you probably want to .find or check wether .some of the objects match your query.
Does "includes" work with objects or do I need to use another
function?
Includes works with objects, but it compares objects by reference.
In your case, despite the first element of itemsObject has the same keys and values of itemToSearch, they are different objects, hence includes will not work, since, for objects cases, it looks for the same object instance.
In order to make it work, you can use several alternatives, like .find, .some, .filter.
Another solution, which I personally don't recommend but I think that it's worth mentioning, is that you can use .includes if you first map items to strings instead. In that case, using JSON.stringify, you can check whether the objects are the same. BEWARE: this will work with single key items only. JSON.stringify doesn't preserve key and values order, so it works with single keys objects only (unless keys and values are in the same order in the original stringified object). Moreover, the JSON.stringify way is way heavier and less performant than the others, I just think it's worth mentioning that as an example.
Below some examples with each of them.
var itemsString = ["name1", "name2"];
var itemsObject = [{ name: "name1" }, { name: "name2" }];
var itemToSearch = { name: "name1" };
console.log(itemsObject.some(r => r.name === itemToSearch.name));
console.log(!!itemsObject.find(r => r.name === itemToSearch.name));
// ^--- !! is used to cast to boolean.
console.log(itemsObject.filter(r => r.name === itemToSearch.name).length > 0);
console.log(itemsObject.map(i => JSON.stringify(i)).includes(JSON.stringify(itemToSearch)));
// ^--------------^ ^---------------------------------------^
// |------ this will stringify each object, converting it to a json string. |
// |
// this will check whether the string[] includes any stringified value.--^
I'm calling an external service and I get the returned domain object like this:
var domainObject = responseObject.json();
This converts the response object into a js object. I can then easily access a property on this object like this
var users = domainObject.Users
Users is a collection of key/value pairs like this:
1: "Bob Smith"
2: "Jane Doe"
3: "Bill Jones"
But CDT shows users as Object type and users[0] returns undefined. So how can I get a handle to the first item in the collection? I'm assuming that some type of type cast is needed but not sure how I should go about doing this
UPDATE
Here is one way I could access the values:
//get first user key
Object.keys(responseObject.json().Users)[0]
//get first user value
Object.values(responseObject.json().Users)[0]
But I need to databind through ng2 so I was hoping for a simpler way like this:
<div>
<div *ngFor="let user of users">
User Name: {{user.value}}
<br>
</div>
</div>
Maybe I should just create a conversion function in my ng2 component which converts the object into what I need before setting the databinding variable?
UPDATED ANSWER
So after scouring through a few docs I found the "newish" Object.entries() javascript function. You can read about it here. Pretty cool.
Anyways, give this a try. I am ashamed to say that I don't have time to test it, but it should get you going in the right direction.
usersArray = []
// Turn Users object into array of [key, value] sub arrays.
userPairs = Object.entries(users);
// Add the users back into an array in the original order.
for (i=0; i < userPairs; i++) {
usersArray.push(_.find(userPairs, function(userPair) { return userPair[0] == i }))
}
ORIGINAL ANSWER
I would use either underscore.js or lodash to do this. Both are super helpful libraries in terms of dealing with data structures and keeping code to a minimum. I would personally use the _.values function in lodash. Read more about it here.. Then you could use users[0] to retrieve the first item.
The only caveat to this is that lodash doesn't guarantee the iteration sequence will be the same as it is when the object is passed in.
users = _.values(users);
console.log(users[0]);
How about this:
let user= this.users.find(() => true)
This should return the "first" one.
If your initial object is just a plain object, how do you know it is sorted. Property members are not sorted, ie: looping order is nor guaranteed. I´d extract the user names into an array and the sort that array by the second word. This should work (as long as surnames are the second word, and only single spaces are used as separators).
var l=[];
for(var x in users) {
push.l(users[x]);
}
var l1=l.sort ( (a,b) => return a.split(" ")[1]<b.split(" ")[1]);
I have an array of json objects who results I want to groupBy() based on multiple properties i.e.,
I have:
[
{
prop1:val1,
prop2:val2,
prop3:val3,
prop4:val4
}
]
Now if I just wanted to group by prop1 I guess I could have done :
_.groupBy(givenArray, 'prop1');
Now what should I do if I have to group by prop1,prop2 and prop3, i.e., (prop1 && prop2 && prop3)
Please guide.
You can put that target values to an array, and then form them to string, or just transform them to string form and combine:
_.groupBy(givenArray, function(item) {
var keys = _.pick(item, 'prop1', 'prop2', 'prop3');
// If all are string or number,
// return keys.join('_#%#_'); // this prevent ['ab', 'a'] generate same key to ['a', 'ba'];
return JSON.stringify(keys);
});
JSON.stringify maybe one of the many ways to create a combined key, I'm not sure what your vals is (string, number or else), so I'll just use stringify here.
It really depends on what you want your final structure to be.
If you don't mind a non-flat object structure, you can do nested _.groupBy calls:
var result = _(givenArray) //begin chain
.groupBy('a')
.mapValues(function(groupedByA) {
return _(groupedByA) //begin chain
.groupBy('b')
.mapValues(function (groupedByAAndB) {
return _.groupBy(groupedByAAndB, 'c');
})
.value(); //end chain
})
.value(); //end chain
//Then you can do things like:
result[5][4][3]; //All items where a=5, b=4, and c=3.
Downside here is that there's an extra level of nesting for each property you group by, and result[5][4] will blow up if there aren't any results where a=5 for example. (Though you could use a library like koalaesce for that)
Alternatively, if you want a flat object structure, but don't mind it being a bit ungainly to access the items, then:
var result = _.groupBy(givenArray, function (item) {
return JSON.stringify(_.pick(item, 'a','b','c'));
});
//Much simpler above, but accessed like:
result[JSON.stringify({a: 5, b:4, c:3})]
Much simpler and scales better to grouping by more things... but awkward to work with, since the keys are full JSON strings; but then you also don't have the null issue that the first option has.
You can also, just use _.values or some equivalent to turn the flat object structure into a single array of arrays. Then there's obviously no "random" access to get all items with a given value of a,b, and c, but you can always loop over it.
Example:
var myArray = ['e', {pluribus: 'unum'}];
How do I get the first 'e'?
My actual array looks like this:
({'U.S., MCC':{score:"88.88", url:"http://ati.publishwhatyoufund.org/donor/usmcc/"}, GAVI:{score:"87.26", url:"http://ati.publishwhatyoufund.org/donor/gavi/"}, 'UK, DFID':{score:"83.49", url:"http://ati.publishwhatyoufund.org/donor/ukdfid/"}, UNDP:{score:"83.38", url:"http://ati.publishwhatyoufund.org/donor/undp/"}, 'World Bank, IDA':{score:"73.81", url:"http://ati.publishwhatyoufund.org/donor/world-bank-ida/"}, 'Global Fund':{score:"70.65", url:"http://ati.publishwhatyoufund.org/donor/global-fund/"}})
I need to get the name. i.e 'U.S., MCC' and then the score and the url - I am using this in highcharts as data points in a graph.
I know it should be simple, but I am a complete JS noob.
Thanks
To get the first element from your array, just use this:
myArray[0]
Notice that 0 points at the first element in the array. Arrays are zero-indexed.
Have a look at this mdn page about arrays to learn more.
However, what you have there isn't an array, it's an object. You can't access those with numeric keys (like 0, 1, 2 etc)
To get the first element of your object, you have to use the "key" to access that value.
Assuming:
var myObject = {'U.S., MCC':{score:"88.88", url:"http://ati.publishwhatyoufund.org/donor/usmcc/"}, GAVI:{score:"87.26", url:"http://ati.publishwhatyoufund.org/donor/gavi/"}, 'UK, DFID':{score:"83.49", url:"http://ati.publishwhatyoufund.org/donor/ukdfid/"}, UNDP:{score:"83.38", url:"http://ati.publishwhatyoufund.org/donor/undp/"}, 'World Bank, IDA':{score:"73.81", url:"http://ati.publishwhatyoufund.org/donor/world-bank-ida/"}, 'Global Fund':{score:"70.65", url:"http://ati.publishwhatyoufund.org/donor/global-fund/"}}
Then:
myObject['U.S., MCC']
Will be:
{score:"88.88", url:"http://ati.publishwhatyoufund.org/donor/usmcc/"}
Or, as a more simple example:
var foo = {
'bar': 1,
'wut': {'nested': 'you can nest objects! (and arrays, etc)'}
baz: 'Objects, woo!', // Quotes around keys aren't mandatory, unless you have
} // spaces in the keys: 'quotes mandatory'
foo['bar'] // 1
foo.wut.nested // 'you can nest objects! (and arrays, etc)'
foo.baz // 'Objects, woo!' (you don't have to use the square brackets,
// if the key is a simple string (No spaces))
Have a look at this mdn article about working with objects to learn more about those.
Now, to actually get the "first" element in that object is tricky, since objects aren't sorted. (Even though they may appear so.)
You can loop through an object using a for...in, but there's no guarantee the items will show up in the same order, on different browsers:
for (var key in myObject) {
if (myObject.hasOwnProperty(key)) { // Make sure it's a proper element on the object, not a prototype function.
// key == ''U.S., MCC', for example,
doSomethingWith(myObject[key]);
}
}
You can iterate over objects in a sorted order, but there's some better answers out there about that.
Try to use this case:
var myObj = {'U.S., MCC':{score:"88.88", url:"http://ati.publishwhatyoufund.org/donor/usmcc/"}, GAVI:{score:"87.26", url:"http://ati.publishwhatyoufund.org/donor/gavi/"}, 'UK, DFID':{score:"83.49", url:"http://ati.publishwhatyoufund.org/donor/ukdfid/"}, UNDP:{score:"83.38", url:"http://ati.publishwhatyoufund.org/donor/undp/"}, 'World Bank, IDA':{score:"73.81", url:"http://ati.publishwhatyoufund.org/donor/world-bank-ida/"}, 'Global Fund':{score:"70.65", url:"http://ati.publishwhatyoufund.org/donor/global-fund/"}};
for(v in myObj) {
console.log("Obj key: "+v, myObj[v]);
}
Javascript newbie here --
I have the following array:
var group = ({
one: value1,
two: value2,
three: value3
});
I want to check if array "group" is part of "groupsArray" and add it if doesn't or remove it if it does.
var groupLocate = $.inArray(group, groupsArray);
if(groupLocate ==-1){
groupsArray.push(group);
} else {
groupsArray.splice($.inArray(group, groupsArray),1);
}
This method works with single value arrays. Unfortunately, I can't get it to work in this case with three keys and values as groupLocate always returns -1.
What am I doing wrong?
Thanks.
First it helps to understand why $.inArray() didn't work. Let's try a simpler case. Paste this in to the JavaScript console in your browser on a page with jQuery loaded (such as this page we're on) and run it:
var object = { a: 1 };
var array = [ { a: 1 } ];
console.log( '$.inArray: ', $.inArray( object, array ) );
(Note the terminology: your group variable is an Object, not an Array.)
Now it looks like object is in the array, right? Why does it print -1 then? Try this:
console.log( object );
console.log( array[0] );
They look the same. How about:
console.log( '== or === works? ', object == array[0], object === array[0] );
Or even simpler:
console.log( 'Does {a:1} == {a:1}? ', {a:1} == {a:1} );
console.log( 'What about {} == {}? ', {} == {} );
Those all print false!
This is because two objects that happen to have the same content are still two separate objects, and when you use == or === to compare two objects, you are actually testing whether they are both references to one and the same object. Two different objects will never compare equal, even if they contain exactly the same content.
$.inArray() works like using an === operator to compare two objects - it won't find an object in an array unless it is the same object, not just an object with identical content.
Knowing this, does that suggest any possible ways to approach the problem? There are several ways you could write your own code to search the array for your object, or you may find it helpful to use a library such as Underscore.js which has many useful methods for arrays and objects.
For example, you could use _.findWhere( groupsArray, group ) to find the first match - with the caveat that it only compares the properties that are in the group object. For example, if group is {a:1}, it would match an object in the groupsArray array that was {a:1,b:2}.
If you need an exact match, you could combine Underscore's _.find() and _.isEqual() methods:
var index = _.find( groupsArray, function( element ) {
return _.isEqual( element, group );
});
Now one last thing to watch out for. Your code that pushes the group object onto the groupsArray array - you know that pushes the actual group object itself. It doesn't make a copy of it in the array, it's a reference to the very same object. (Ironically, this means that your original code to find group in the array would actually work in the case where you'd pushed that same group object onto the array yourself.)
If you want to make sure the elements in groupsArray are each their own independent object, and not a reference to another object floating around in your code, you can use another Underscore method to do a shallow copy:
groupsArray.push( _.clone(group) );
If group has any nested objects, though, this won't copy them. (I don't see a deep copy function in Underscore, although you could write one if you need it.)