How to find an object within a JS collection by value - javascript

I am working with an API right now and I am using details[5].Value to target information in the following format:
details:
"value":[
{
"ID": "6",
"Name": "Links",
"Value": "URL"
},
{
"ID": "7",
"Name": "Other",
"Value": "URL"
}
etc
]
The problem is that the location inside of the JSON response is likely to change in the future, making my code obsolete and as the url has the potential to change as well, I cannot target that.
I want a way to target the value of url, mostly, because of this, by the value of the "Name" property. However, if I use something like
_.where(details, { Name: "Links" }).Value
It comes back as undefined. I am not sure if there would be another way to get to the information?

There are a couple points of confusion here.
_.where returns an array:
Looks through each value in the list, returning an array of all the values that contain all of the key-value pairs listed in properties.
so your _.where(details, obj).Value will (almost) always give you undefined because an array is unlikely to have a Value property. _.findWhere on the other hand does return a single value:
Looks through the list and returns the first value that matches all of the key-value pairs listed in properties.
Secondly, your details appears to look like:
let details = {
value: [
{ ID: '6', Name: 'Links', Value: 'URL' },
{ ID: '7', Name: 'Other', Value: 'URL' },
...
]
}
so you don't want to search details, you want to search details.value.
Putting them together:
_(details.value).findWhere({ Name: 'Links' }).Value
or
_.findWhere(details.value, { Name: 'Links' }).Value
You could use Array.prototype.find (or Array.prototype.filter if you're looking for all matches) and write your own callback but you already have Underscore available so why bother? Furthermore, Backbone collections have findWhere and where methods and there are advantages to matching Backbone's overall terminology.

Take a look at this mini function. Let me know if there is something wrong
Update
This is the ES5 Version
function f(key, value, array){
return array.value.filter(function(sub_array){
return sub_array[key] == value;
});
}
This is the ES6 Golfed Version
f=(k,v,a)=>a.value.filter(_=>_[k]==v)
//This is your JSON
var details = {
value: [
{
"ID": "6",
"Name": "Links",
"Value": "URL"
},
{
"ID": "7",
"Name": "Other",
"Value": "URL"
}
]
}
// Short code
f=(k,v,a)=>a.value.filter(_=>_[k]==v)
// f is the function name
// Recives k = array key, v = value, a = array
// filter by the given key and value
// return the result as an array
console.log(f('Name', 'Links', details))

An alternative is using the Javascript built-in function find to get a specific object within an array.
This alternative allows you to pass either an object or a string.
If the byThis parameter is an object, the whole set of key-values must match with the key-values of every object within the target array.
Otherwise, if byThis is a string every object will be treated as string to make the necessary comparison.
let details = { "value": [{ "ID": "6", "Name": "Links", "Value": "URL" }, { "ID": "7", "Name": "Other", "Value": "URL" }]};
let findBy = (array, byThis) => {
return array.find(o => {
if (typeof byThis === 'object') return Object.keys(byThis).every(k => o[k] === byThis[k]);
else if (typeof byThis === 'string') return o.toString() === byThis;
});
}
let found = findBy(details.value, {Name: "Links"});
console.log(found);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Related

IndexOf over a nested array response graph returning just []

I am trying to filter some articles from a graphql response, by articleTag. Se my structure below:
{
"id": "41744081",
"articleTitle": "text",
"articleContent": "text",
"categoryName": { "categoryName": "Company", "id": "38775744" },
"articleTags": [
{ "articleTag": "event", "id": "37056861" },
{ "articleTag": "car", "id": "37052481" },
]
},
{
"id": "41754317",
"articleTitle": "text",
"articleContent": "text",
"categoryName": { "categoryName": "Sales and Martketing", "id": "38775763" },
"articleTags": [{ "articleTag": "technology", "id": "37056753" }]
},
...
But when applying my function:
notificationFiltered () {
var articleResponse = this.response.allArticles;
var routeParam = this.$route.params.tagName; //contains the id of the tag
const filteredTag = articleResponse.filter((item) => {
return (item.articleTags.indexOf(routeParam) >= 0);
});
console.log(filteredTag);
},
When I'm "console.log" the result I get only a "[]". Not sure if is related with the way of query is being render, in the API I get the same formation but with this slightly difference
{
"data": {
"allArticles": [... the specify structure above]
}
}
while printing that with vue {{response.allArticles}} I just get the first structure, I think it shouldn't matter?
Thanks in advance for the advice
You won't be able to use indexOf for array of objects to find a matching object - only strict equality is needed, and that's hard to get in the reference land. Consider this:
const objs = [
{ foo: 'bar' },
{ foo: 'baz' },
{ foo: 'foo' } // whatever
];
const needle = { foo: 'baz' };
objs.indexOf(needle);
// -1
What? Yes, there's an object looking exactly like needle in that array - but it's a different object:
objs[1] === needle; // false
That's why indexOf just goes past that one - and gives out -1, a "not found" result.
What you should be able to use in this case is findIndex. Still you need to build the predicate to have a match. For example:
const objs = [
{ foo: 'bar' },
{ foo: 'baz' },
{ foo: 'foo' }
];
const needle = { foo: 'baz' };
objs.findIndex(el => JSON.stringify(el) === JSON.stringify(needle));
// 1
In this example, comparing results of JSON.stringify in the predicate function is a poor man's _.isEqual - just to illustrate the concept. What you should consider actually using in your code is either _.isEqual itself, or similar function available in toolkit of your choice.
Alternatively, you can just check for specific fields' values:
objs.findIndex(el => el.foo === needle.foo); // still 1
This will apparently find objects even if their other properties do not match though.

Loop through an array of objects and get where object.field equals value

I'm currently working on a small application where I have to loop through an enormous array of objects. What would be the most efficient method to perform this?
var array = [
{
id: "1",
name: "Alpha"
},
{
id: "2",
name: "Beta"
},
...
];
I'd like to get each object where name equals "Alpha". I'm currently using a simple if statement to filter the objects with a different name value out, but I wonder if there's a more efficient way to do this, performance-wise.
It's worth to mention that I'll push the matching results into a new array.
No, there is no more efficient way.
The alternative is to build and maintain some kind of internal data structure which allows you to find the desired elements faster. As usual, the trade off is between the work involved in maintaining such a structure vs the time it saves you.
I don't have any way about which I would know it's more effective.
But if you had your objects ordered by name you could stop your search imideatly upon reaching an object whose name is not equal to "Alpha".
To find the first object you're looking for you can use binary search and from this Object you go up and down until at both ends you reach an object which isn't named "Alpha" or the end of array.
This is only a way of optimizing and will require time to sort the array and also will take more time when adding an element.
There's a JavaScript function exactly for this kind of task. Filter
From the Docs
The filter() method creates a new array with all elements that pass the test implemented by the provided function.
Here is a small example by code for getting all element from array which has a certain 'name' field:
const arr = [
{name: 'Abc'},
{name: 'Xyz'},
{name: 'Lmn'},
{name: 'Xyz'},
{name: 'Xyz'}
];
let response = findByName('Xyz');
console.log(response);
function findByName (name) {
return arr.filter((element) => {
return element.name = name;
});
}
If you need more than one time a collection with a given name, you could use an object with the names as hashes and have instantly access to the items.
var array = [{ id: "1", name: "Alpha" }, { id: "2", name: "Beta" }, { id: "3", name: "Beta" }, { id: "4", name: "Gamma" }, { id: "5", name: "Beta" }, { id: "2", name: "Alpha" }],
hash = Object.create(null);
array.forEach(function (a) {
if (!hash[a.name]) {
hash[a.name] = [];
}
hash[a.name].push(a);
});
console.log(hash);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Turn javascript dictionary into JSON format

I have a javascript dictionary:
{
"a": {
"b": {
"c": null,
"d": null
}
}
}
How can I turn it into a JSON object which I can specify the name and children property? Is there any elegant way to do it?
The JSON object could be:
{
name:"a"
children: [{
name:"b",
children: [{
name:"c",
children: null
},{
name:"d",
children: null}]
}]
}
You could create a recursive function for generating your output:
var x = {
"a": {
"b": {
"c": null,
"d": null
}
}
};
function generate(item, key) {
var result = {
name: key,
children: []
};
for (var _ in item)
result.children.push(generate(item[_], _))
if (result.children.length == 0)
result.children = null;
return (key == undefined) ? result.children : result;
}
console.log(JSON.stringify(generate(x), null, 1));
Output:
[
{
"name": "a",
"children": [
{
"name": "b",
"children": [
{
"name": "c",
"children": null
},
{
"name": "d",
"children": null
}
]
}
]
}
]
The above generate function returns a list instead of a dictionary, because it's possible to have more than one name at the root level. But if we are sure that we have only one name at the root name, we can generate the json like this:
console.log(JSON.stringify(generate(x)[0], null, 1));
Here's my solution. It's similar to JuniorCompressor's.
function generate(obj) {
// Return primitives as-is
if (!(obj instanceof Object)) return obj;
// Loop through object properties and generate array
var result = [];
for (var key in obj) {
result.push({
name: key,
children: generate(obj[key])
});
}
// Return resulting array
return result;
}
As mentioned, the resulting object will actually be an array (in case there is more than one root-level property in the original object). If you really need the resulting object to be an object with only properties name and value, then you should access the first element of the resulting array, like this:
generate(obj)[0]
Solution
You need a recursive function, which calls itself for children. Note that in your example, there is only one top-level child (a). I instead use the assumption that the top-level 'name' refers to the name of the actual object itself. If you want to get results exactly like you demonstrate, from an object called 'obj', run toJSON(obj).children[0]. For the overall function, try something like the following:
function toJSON(obj, name) {
var subTree = {
name: name,
children: []
};
if (obj !== null && Object.keys(obj).length >= 1) {
for (var child in obj) {
subTree.children.push(toJSON(obj[child], child));
}
} else {
subTree.children = null;
}
return subTree;
}
Results of toJSON(obj).children[0]:
{
"name": "a",
"children": [{
"name": "b",
"children": [{
"name": "c",
"children": null
},{
"name": "d",
"children": null
}]
}]
}
Results of toJSON(obj, 'obj'):
{
"name": "obj",
"children": [{
"name": "a",
"children": [{
"name": "b",
"children": [{
"name": "c",
"children":null
},
{
"name": "d",
"children": null
}]
}]
}]
}
Here's a line-by-line explanation:
Declares the function, which expects two arguments: the object, and it's name. If you're going to be using toJSON(obj).children[0], though, don't bother with the second argument. It won't affect the result.
Declares the result, an object containing information about the current level and all levels below in the object. If you consider the object a tree, this result contains information about the current branch, and all it's branches.
Declares the property 'name', containing the name/key of the object at the current level. When you call the function, you need to include the name as second argument because there is no way of dynamically finding the name of a variable. They're passed into functions by value. As described above, though, if you're looking for results EXACTLY like those in your example, you're going to use toJSON(obj).children[0], instead of toJSON(obj, 'obj'), and then don't need to bother with the second argument.
Declares the children array, to be filled below
Terminates the declaration begun on Line 2
Checks if the object ISN'T null, and that it has children, using a handy method of the Object built-in object, running Lines 7, 8 and 9 if so
Iterates over the children of the object, running Line 8 for each child
Recursively runs the toJSON() function for each child, to get it's subTree. Because the children can't dynamically figure out their own names, it passes those in as well.
Terminates the for loop begun at Line 7
If there are no children, run Line 11. This is only run if Lines 7, 8 and 9 are not.
Sets children to null (only run if there are no children, as checked by Line 6)
Terminates the else started at line 10
Returns the current subTree, either to the function if called recursively by the function, or to you if you called it yourself
Terminates the function
Information about the Previous Version, Pre-edit
The original function only used one argument, whereas that above has another argument for 'name'. This is because the original tried to figure out the name of each level within that same level, which I have since realized isn't possible in Javascript. Basically, the original didn't work, and an extra argument had to be added to make it work. For records' sake, though, here was the original function:
// THIS FUNCTION DOESN'T WORK. IT'S HERE ONLY FOR HISTORICAL ACCURACY:
function toJSON(obj) {
var subTree = {
name: obj.constructor.name, // This should get the object key
children: []
};
if (Object.keys(obj).length >= 1) { // If there is at least one child
for (var child in obj) {
subTree.children.push(toJSON(obj[child]));
}
} else {
subTree.children = null;
}
return subTree;
}

Search through multidimensional array JavaScript

I got stuck trying to retrive array items. So here is the deal. I have a two dimentional array which has value and key so example of my data is:
[
Object { css="SS", title="Apple"},
Object { css="SS", title="Orange"},
Object { css="SS", title="Banana"}
]
I want to see if an object exists in the array above. And I have no idea why its not working, here is my code to find the object:
jQuery.inArray("Apple", fruits["title"]); //this returns -1 why?
Any ideas how to search two dimensional array?
This is not a 2D array, this is an array of objects, so this should work:
for (var i = 0; i < array.length; i++) {
console.log(array[i].title); //Log the title of each object.
if (array[i].title == "Apple") {
console.log("Found apple!");
}
}
Also, objects are key/val pairs, denoted by key : val, not key = val. Your array has syntax errors and shouldn't run.
To be pedantic, you have an array of objects, not a 2d array. Also your syntax for the object parameters is incorrect.
You can use filter() on the array to find the values:
var array = [
{ css: "SS", title: "Apple"},
{ css: "SS", title: "Orange"},
{ css: "SS", title: "Banana"}
];
var matches = array.filter(function (obj) { return obj.title == "Apple" });
if (matches.length) {
// Apple was in the array...
}
If you have an object like this
var peoples = [
{ "name": "bob", "dinner": "pizza" },
{ "name": "john", "dinner": "sushi" },
{ "name": "larry", "dinner": "hummus" }
];
Ignore what's below. Use the filter method!
peoples.filter(function (person) { return person.dinner == "sushi" });
// => [{ "name": "john", "dinner": "sushi" }]
You can search for people who have "dinner": "sushi" using a map
peoples.map(function (person) {
if (person.dinner == "sushi") {
return person
} else {
return null
}
}); // => [null, { "name": "john", "dinner": "sushi" }, null]
or a reduce
peoples.reduce(function (sushiPeople, person) {
if (person.dinner == "sushi") {
return sushiPeople.concat(person);
} else {
return sushiPeople
}
}, []); // => [{ "name": "john", "dinner": "sushi" }]
I'm sure you are able to generalize this to arbitrary keys and values!
fruits probably is a array, fruits["title"] therefor doesn't exist.
You might want to transform your data:
var fruitTitles = fruits.map(function(f) { return f.title; });
jQuery.inArray("Apple", fruitTitles);
From the jQuery docs:
jQuery.inArray( value, array [, fromIndex ] )
I've never used this method, but a quick guess:
var hasIt = jQuery.inArray({css:"SS",title:"Apple"}, myArray);
As the $.inArray() documentation explains, the first argument to the function is the value to search for. Your array does not have any elements that are equal to the string "Apple" that you have supplied in the first argument because none of your array elements are strings (they're all objects).
The second argument to $.inArray() is supposed to be the array itself, but (assuming fruits is the array you show) fruits["title"] is undefined because your array has no property called "title", only the objects in the array have that property.
Try this instead:
var index = $.inArray("Apple", $.map(fruits, function(el) { return el.title; }));
try this code
var fruits = [
{ css:"SS", title:"Apple"},
{ css:"SS", title:"Orange"},
{ css:"SS", title:"Banana"}
];
jQuery.grep(fruits,function(fruit){return fruit.title == 'Apple'});

Using jQuery $.each with Json erroring with 1 result

Basically I am transforming a JSON result into html and using $.each it iterate through multiple keys. For example, I am pulling back facebook posts and iterating through the likes in that post.
The problem lies in the fact that when there are multiple "likes" everything works great! although when there is only 1 "like" the "source" key is removed from the result set and my javascript breaks because I expect it to be there. Any idea why the $.each is skipping a level for single nodes? The following is my code:
* JQUERY **
$.each(post.likes.item, function(i, like){
$(currentpost).find('div.cc_likes').append(like + ',');
console.log(like)
});
* JSON RESULT **
* Single Like
likes": {
"item": {
"source": {
"cta": "Mary Smith",
"url": "http:\/\/www.facebook.com\/",
"photo": {
"image": "https:\/\/graph.facebook.com\/"
}
}
},
Result in console:
Object
cta: "MaryAnn Smith"
photo: Object
url: "http://www.facebook.com/"
* Multiple Likes
"likes": {
"item": [
{
"source": {
"cta": "Bobby Carnes Sr.",
"url": "http:\/\/www.facebook.com",
"photo": {
"image": "https:\/\/graph.facebook.com\"
}
}
},
{
"source": {
"cta": "Jenna Purdy",
"url": "http:\/\/www.facebook.com\",
"photo": {
"image": "https:\/\/graph.facebook.com\"
}
}
},
{
"source": {
"cta": "Kevin Say",
"url": "http:\/\/www.facebook.com\",
"photo": {
"image": "https:\/\/graph.facebook.com\"
}
}
}
],
"count": "10",
"count_display": "10"
},
Result in console:
Object
source: Object
cta: "Kevin Smith"
photo: Object
url: "http://www.facebook.com/"
Since $.each() needs an array or array like object as argument, before using the object post.likes.item check if it is an array of not.
Following code will always pass an array to jQuery -
$.each([].concat(post.likes.item), function(i, like){
$(currentpost).find('div.cc_likes').append(like + ',');
console.log(like)
});
Explanation
[] is an empty array in JavaScript. Every array in JavaScript has a concat method.
[].concat(obj) concats obj to the empty array and returns an array.
if obj is not an array, result is [obj] which is an array with one item.
if obj is an array, then result is a deep copy of obj which is already an array.
More about concat method
if ( isArray ) {
for ( ; i < length; i++ ) {
value = callback.call( obj[ i ], i, obj[ i ] );
if ( value === false ) {
break;
}
}
} else {
for ( i in obj ) {
value = callback.call( obj[ i ], i, obj[ i ] );
if ( value === false ) {
break;
}
}
}
That is the jquery code being run on your JSON return. What's happening is, when you are looking at multiple results, it is looping through the array, return each base level object. However, when you are running it on a single return, it is looping through the object properties(in this case, "source"), and returning the value of that property.
You have two choices here. You can either make sure single items are still put in an array, or you can do a check for single items on the client side. The way Moazzam Khan suggests is the best way to do it in most cases.

Categories