How to build a menu list object recursively in JavaScript? - javascript

With an array of
['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'];
I'd like to construct an map object that looks like:
{
'social': {
swipes: {
women: null,
men: null
}
},
'upgrade': {
premium: null
}
}
const menu = ['/social/swipes/women', '/social/likes/men', '/upgrade/premium'];
const map = {};
const addLabelToMap = (root, label) => {
if(!map[root]) map[root] = {};
if(!map[root][label]) map[root][label] = {};
}
const buildMenuMap = menu => {
menu
// make a copy of menu
// .slice returns a copy of the original array
.slice()
// convert the string to an array by splitting the /'s
// remove the first one as it's empty
// .map returns a new array
.map(item => item.split('/').splice(1))
// iterate through each array and its elements
.forEach((element) => {
let root = map[element[0]] || "";
for (let i = 1; i < element.length; i++) {
const label = element[i];
addLabelToMap(root, label)
// set root to [root][label]
//root = ?
root = root[label];
}
});
}
buildMenuMap(menu);
console.log(map);
But I'm unsure how to switch the value of root.
What do I set root to so that it recursively calls addLabelToMap with
'[social]', 'swipes' => '[social][swipes]', 'women' => '[social][swipes]', 'men'?
I've used root = root[element] but it's giving an error.
Alternative solutions would be great, but I'd like to understand why this isn't working fundamentally.

This problem is about creating the object and maintaining it's state while looping through input array and splitting string based upon /.
This can be accomplished using Array.reduce where we start with empty object and while looping through input we start filling it and for last word in every string we assign the value null to object property.
let input = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'];
let output = input.reduce((o, d) => {
let keys = d.split('/').filter(d => d)
keys.reduce((k, v, i) => {
k[v] = (i != keys.length - 1)
? k[v] || {}
: null
return k[v]
}, o)
return o
}, {})
console.log(output)

Use reduce instead of map. The root will be the accumulator in this case:
const buildMenuMap = menu =>
menu.reduce((root, item) => {
let parts = item.slice(1).split("/");
let lastPart = parts.pop();
let leaf = parts.reduce((acc, part) => acc[part] || (acc[part] = {}), root);
leaf[lastPart] = null;
return root;
}, Object.create(null));
Explanation:
For each item in the menu array, we extract the parts by first getting rid of the leading '/' (using slice(1)) and then splitting by '/'.
We then remove the lastPart from this resulting array (the last part is handled separetely from the rest).
For each remaining part in the parts array, we traverse the root array. At each level of traversing, we either return the object at that level acc[part] if it already exists, or we create and return a new one if it doesn't (acc[part] = {}).
After we get to the the last level leaf, we use the lastPart to set the value as null.
Notice that we pass Object.create(null) to reduce. Object.create(null) creates a prototypeless object so it will ba safer to use root[someKey] without having to check if someKey is an owned property or not.
Example:
const buildMenuMap = menu =>
menu.reduce((root, item) => {
let parts = item.slice(1).split("/");
let lastPart = parts.pop();
let leaf = parts.reduce((acc, part) => acc[part] || (acc[part] = {}), root);
leaf[lastPart] = null;
return root;
}, Object.create(null));
let arr = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'];
let result = buildMenuMap(arr);
console.log(result);

It is as easy as:
root = root[label];
if you change your helper function to:
const addLabelToMap = (root, label) => {
if(!root[label]) root[label] = {};
}
I'd write it as:
const buildMenuMap = menus => {
const root = {};
for(const menu of menus) {
const keys = menu.split("/").slice(1);
const prop = keys.pop();
const obj = keys.reduce((curr, key) => curr[key] || (curr[key] = {}), root);
obj[prop] = null;
}
return root;
}

I just debugged your code to see what was wrong and I urge you to do the same. You make two (obvious) mistakes:
Firstly, In the very first iteration, here the value of map is just an empty object {}, the value of root gets initialised to "" and label is swipes.
.forEach((element) => {
let root = map[element[0]] || "";
...
root = root[label];
}
So then you get root[label] is undefined and so the new root is undefined.
Second, you are using map everywhere as it is.
const addLabelToMap = (root, label) => {
if(!map[root]) map[root] = {};
if(!map[root][label]) map[root][label] = {};
}
Instead you should be taking it as a parameter, for you to be able to do a recursion.
const addLabelToMap = (root, label) => {
if(!root[label]) root[label] = {};
}
To debug you code, create a simple HTML file with the js in the script tags and then serve it from your local machine using python -m http.server. You can then add a debug point and go through your code step by step.

Try this as a holistic solution:
const menu = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'];
const deepMerge = (target, source) => {
// Iterate through `source` properties and if an `Object` set property to merge of `target` and `source` properties
for (let key of Object.keys(source)) {
if (source[key] instanceof Object && key in target) Object.assign(source[key], deepMerge(target[key], source[key]))
}
// Join `target` and modified `source`
Object.assign(target || {}, source)
return target
};
const buildMenuMap = menu => {
return menu
.map(item => item.split('/').splice(1))
// The `root` value is the object that we will be merging all directories into
.reduce((root, directory) => {
// Iterates backwards through each directory array, stacking the previous accumulated object into the current one
const branch = directory.slice().reverse().reduce((acc, cur) => { const obj = {}; obj[cur] = acc; return obj;},null);
// Uses the `deepMerge()` method to stitch together the accumulated `root` object with the newly constructed `branch` object.
return deepMerge(root, branch);
}, {});
};
buildMenuMap(menu);
Note: The deep merge solution was taken from #ahtcx on GitHubGist

You can simplify your code using Array.reduce, Object.keys & String.substring
buildMenuMap
The function takes the array as input and reduce it into an object where for each entry in array, the object is updated with corresponding hierarchy using addLabelToMap function. Each entry is converted into an array of levels (c.substring(1).split("/")).
addLabelToMap
The function takes 2 inputs
obj - the current root object / node
ar - array of child hierarchy
and returns the updated object
Logic
function pops the first value (let key = ar.shift()) as key and add / update in the object (obj[key] = obj[key] || {};).
If there is child hierarchy of current object (if(ar.length)), recursively call the function to update the object till the end (addLabelToMap(obj[key], ar)).
Else (no further child hierarchy), check whether the object has some hierarchy (else if(!Object.keys(obj[key]).length)) because of other entries in array. If there is no hierarchy, i.e. it is a leaf, hence, set the value to null (obj[key] = null). Note, if there will never be case where there is an entry in array like /social/swipes/men/young along with existing, the else if block can be simplified to a simple else block.
The object has been update, return the final updated object
let arr = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'];
function addLabelToMap(obj, ar) {
let key = ar.shift();
obj[key] = obj[key] || {};
if(ar.length) addLabelToMap(obj[key], ar);
else if(!Object.keys(obj[key]).length) obj[key] = null;
return obj;
}
function buildMenuMap(ar) {
return ar.reduce((a,c) => addLabelToMap(a,c.substring(1).split("/")), {});
}
console.log(buildMenuMap(arr));

You can solve this also with a recursive function in a concise way like this:
let obj={}, input = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'];
const makeObj = (arr, obj={}) => {
let prop = arr.shift()
prop in obj ? null : Object.assign(obj, {[prop]: {}})
arr.length ? makeObj(arr, obj[prop]) : obj[prop] = null
return obj
}
input.forEach(x => makeObj(x.split('/').filter(Boolean), obj))
console.log(obj)
The idea is for each of the paths to pass them to a makeObj function which will decorate an object with the paths recursively until it reaches the end of the path array. This is another alternative to the common Array.reduce approach.

Citate from bounty description:
The current answers do not contain enough detail.
I think you do not understand how it works in current answers. Because of this I will provide you two solutions: one alternative solution and one expanded solution with Array.reduce() function.
Alternative solution with for loops
The explanation of code see in the code comments.
var arr = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'],
result = {};
//if you want to have it shorter you can write for(var i = arr.length; i--;) too
for(var i = 0; i < arr.length; i++)
{
var parts = arr[i].slice(1).split('/'),
//if we have already one object then we take it. If not the we create new one:
curObj = result[parts[0]] = result[parts[0]] || {};
for(var k = 1; k < parts.length; k++)
{
//if we have next part
if(parts[k+1])
//if we have already one object then we take it. If not the we create new one:
curObj[parts[k]] = curObj[parts[k]] || {};
//if we do not have next part
else curObj[parts[k]] = null;
//or if-else block in one line:
//curObj[parts[k]] = parts[k+1] ? (curObj[parts[k]] || {}) : null;
//link to next object:
curObj = curObj[parts[k]];
}
}
console.log(JSON.stringify(result, null, 4));
But if you did not understand it then see this code:
var arr = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'],
result = {},
parts = arr[2].slice(1).split('/'),
curObj = result[parts[0]] = {};
curObj[parts[1]] = parts[1+1] ? {} : null;
console.log(JSON.stringify(result, null, 4));
Expanded solution with Array.reduce()
In this solution I use the code from user Nitish Narang in expanded version with some explanation in comments and console output – so yuo can see in console what the code does. My recommendation: if you do not understand the code with arrow functions then write it full with normal functions and appropriate variable names which explain themselves. We (humans) need some pictures to imagine all things. If we have only some short variable names then it is difficalt to imagine und undestand all this. I have also a little bit «shorted» his code.
var arr = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'];
var result = arr.reduce(function(acc0, curVal, curIdx)
{
console.log('\n' + curIdx + ': ------------\n'
+ JSON.stringify(acc0, null, 4));
var keys = curVal.slice(1).split('/');
keys.reduce(function(acc1, currentValue, currentIndex)
{
acc1[currentValue] = keys[currentIndex+1]
? acc1[currentValue] || {}
: null;
return acc1[currentValue]
}, acc0); //acc0 - initialValue is the same object, but it is empty only in first cycle
return acc0
}, {}); // {} - initialValue is empty object
console.log('\n------result------\n'
+ JSON.stringify(result, null, 4));

Related

Javascript, how to filter all the passwords from a API nested in an array [duplicate]

I've got an array:
myArray = [{'id':'73','foo':'bar'},{'id':'45','foo':'bar'}, etc.]
I'm unable to change the structure of the array. I'm being passed an id of 45, and I want to get 'bar' for that object in the array.
How do I do this in JavaScript or using jQuery?
Use the find() method:
myArray.find(x => x.id === '45').foo;
From MDN:
The find() method returns the first value in the array, if an element in the array satisfies the provided testing function. Otherwise undefined is returned.
If you want to find its index instead, use findIndex():
myArray.findIndex(x => x.id === '45');
From MDN:
The findIndex() method returns the index of the first element in the array that satisfies the provided testing function. Otherwise -1 is returned.
If you want to get an array of matching elements, use the filter() method instead:
myArray.filter(x => x.id === '45');
This will return an array of objects. If you want to get an array of foo properties, you can do this with the map() method:
myArray.filter(x => x.id === '45').map(x => x.foo);
Side note: methods like find() or filter(), and arrow functions are not supported by older browsers (like IE), so if you want to support these browsers, you should transpile your code using Babel (with the polyfill).
As you are already using jQuery, you can use the grep function which is intended for searching an array:
var result = $.grep(myArray, function(e){ return e.id == id; });
The result is an array with the items found. If you know that the object is always there and that it only occurs once, you can just use result[0].foo to get the value. Otherwise you should check the length of the resulting array. Example:
if (result.length === 0) {
// no result found
} else if (result.length === 1) {
// property found, access the foo property using result[0].foo
} else {
// multiple items found
}
Another solution is to create a lookup object:
var lookup = {};
for (var i = 0, len = array.length; i < len; i++) {
lookup[array[i].id] = array[i];
}
... now you can use lookup[id]...
This is especially interesting if you need to do many lookups.
This won't need much more memory since the IDs and objects will be shared.
ECMAScript 2015 (JavaScript ES6) provides the find()
method on arrays:
var myArray = [
{id:1, name:"bob"},
{id:2, name:"dan"},
{id:3, name:"barb"},
]
// grab the Array item which matchs the id "2"
var item = myArray.find(item => item.id === 2);
// print
console.log(item.name);
It works without external libraries. But if you want older browser support you might want to include this polyfill.
Underscore.js has a nice method for that:
myArray = [{'id':'73','foo':'bar'},{'id':'45','foo':'bar'},etc.]
obj = _.find(myArray, function(obj) { return obj.id == '45' })
I think the easiest way would be the following, but it won't work on Internet Explorer 8 (or earlier):
var result = myArray.filter(function(v) {
return v.id === '45'; // Filter out the appropriate one
})[0].foo; // Get result and access the foo property
Try the following
function findById(source, id) {
for (var i = 0; i < source.length; i++) {
if (source[i].id === id) {
return source[i];
}
}
throw "Couldn't find object with id: " + id;
}
myArray.filter(function(a){ return a.id == some_id_you_want })[0]
A generic and more flexible version of the findById function above:
// array = [{key:value},{key:value}]
function objectFindByKey(array, key, value) {
for (var i = 0; i < array.length; i++) {
if (array[i][key] === value) {
return array[i];
}
}
return null;
}
var array = [{'id':'73','foo':'bar'},{'id':'45','foo':'bar'}];
var result_obj = objectFindByKey(array, 'id', '45');
Performance
Today 2020.06.20 I perform test on MacOs High Sierra on Chrome 81.0, Firefox 77.0 and Safari 13.1 for chosen solutions.
Conclusions for solutions which use precalculations
Solutions with precalculations (K,L) are (much much) faster than other solutions and will not be compared with them - probably they are use some special build-in browser optimisations
surprisingly on Chrome and Safari solution based on Map (K) are much faster than solution based on object {} (L)
surprisingly on Safari for small arrays solution based on object {} (L) is slower than traditional for (E)
surprisingly on Firefox for small arrays solution based on Map (K) is slower than traditional for (E)
Conclusions when searched objects ALWAYS exists
solution which use traditional for (E) is fastest for small arrays and fast for big arrays
solution using cache (J) is fastest for big arrays - surprisingly for small arrays is medium fast
solutions based on find (A) and findIndex (B) are fast for small arras and medium fast on big arrays
solution based on $.map (H) is slowest on small arrays
solution based on reduce (D) is slowest on big arrays
Conclusions when searched objects NEVER exists
solution based on traditional for (E) is fastest on small and big arrays (except Chrome-small arrays where it is second fast)
solution based on reduce (D) is slowest on big arrays
solution which use cache (J) is medium fast but can be speed up if we save in cache also keys which have null values (which was not done here because we want to avoid unlimited memory consumption in cache in case when many not existing keys will be searched)
Details
For solutions
without precalculations: A
B
C
D
E
F
G
H
I
J (the J solution use 'inner' cache and it speed depend on how often searched elements will repeat)
with precalculations
K
L
I perform four tests. In tests I want to find 5 objects in 10 loop iterations (the objects ID not change during iterations) - so I call tested method 50 times but only first 5 times have unique id values:
small array (10 elements) and searched object ALWAYS exists - you can perform it HERE
big array (10k elements) and searched object ALWAYS exist - you can perform it HERE
small array (10 elements) and searched object NEVER exists - you can perform it HERE
big array (10k elements) and searched object NEVER exists - you can perform it HERE
Tested codes are presented below
function A(arr, id) {
return arr.find(o=> o.id==id);
}
function B(arr, id) {
let idx= arr.findIndex(o=> o.id==id);
return arr[idx];
}
function C(arr, id) {
return arr.filter(o=> o.id==id)[0];
}
function D(arr, id) {
return arr.reduce((a, b) => (a.id==id && a) || (b.id == id && b));
}
function E(arr, id) {
for (var i = 0; i < arr.length; i++) if (arr[i].id==id) return arr[i];
return null;
}
function F(arr, id) {
var retObj ={};
$.each(arr, (index, obj) => {
if (obj.id == id) {
retObj = obj;
return false;
}
});
return retObj;
}
function G(arr, id) {
return $.grep(arr, e=> e.id == id )[0];
}
function H(arr, id) {
return $.map(myArray, function(val) {
return val.id == id ? val : null;
})[0];
}
function I(arr, id) {
return _.find(arr, o => o.id==id);
}
let J = (()=>{
let cache = new Map();
return function J(arr,id,el=null) {
return cache.get(id) || (el=arr.find(o=> o.id==id), cache.set(id,el), el);
}
})();
function K(arr, id) {
return mapK.get(id)
}
function L(arr, id) {
return mapL[id];
}
// -------------
// TEST
// -------------
console.log('Find id=5');
myArray = [...Array(10)].map((x,i)=> ({'id':`${i}`, 'foo':`bar_${i}`}));
const mapK = new Map( myArray.map(el => [el.id, el]) );
const mapL = {}; myArray.forEach(el => mapL[el.id]=el);
[A,B,C,D,E,F,G,H,I,J,K,L].forEach(f=> console.log(`${f.name}: ${JSON.stringify(f(myArray, '5'))}`));
console.log('Whole array',JSON.stringify(myArray));
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>
This snippet only presents tested codes
Example tests results for Chrome for small array where searched objects always exists
As others have pointed out, .find() is the way to go when looking for one object within your array. However, if your object cannot be found using this method, your program will crash:
const myArray = [{'id':'73','foo':'bar'},{'id':'45','foo':'bar'}];
const res = myArray.find(x => x.id === '100').foo; // Uh oh!
/*
Error:
"Uncaught TypeError: Cannot read property 'foo' of undefined"
or in newer chrome versions:
Uncaught TypeError: Cannot read properties of undefined (reading 'foo')
*/
This can be fixed by checking whether the result of .find() is defined before using .foo on it. Modern JS allows us to do this easily with optional chaining, returning undefined if the object cannot be found, rather than crashing your code:
const myArray = [{'id':'73','foo':'bar'},{'id':'45','foo':'bar'}];
const res = myArray.find(x => x.id === '100')?.foo; // No error!
console.log(res); // undefined when the object cannot be found
If you do this multiple times, you may set up a Map (ES6):
const map = new Map( myArray.map(el => [el.id, el]) );
Then you can simply do a O(1) lookup:
map.get(27).foo
You can get this easily using the map() function:
myArray = [{'id':'73','foo':'bar'},{'id':'45','foo':'bar'}];
var found = $.map(myArray, function(val) {
return val.id == 45 ? val.foo : null;
});
//found[0] == "bar";
Working example: http://jsfiddle.net/hunter/Pxaua/
Using native Array.reduce
var array = [ {'id':'73' ,'foo':'bar'} , {'id':'45' ,'foo':'bar'} , ];
var id = 73;
var found = array.reduce(function(a, b){
return (a.id==id && a) || (b.id == id && b)
});
returns the object element if found, otherwise false
You can use filters,
function getById(id, myArray) {
return myArray.filter(function(obj) {
if(obj.id == id) {
return obj
}
})[0]
}
get_my_obj = getById(73, myArray);
While there are many correct answers here, many of them do not address the fact that this is an unnecessarily expensive operation if done more than once. In an extreme case this could be the cause of real performance problems.
In the real world, if you are processing a lot of items and performance is a concern it's much faster to initially build a lookup:
var items = [{'id':'73','foo':'bar'},{'id':'45','foo':'bar'}];
var lookup = items.reduce((o,i)=>o[i.id]=o,{});
you can then get at items in fixed time like this :
var bar = o[id];
You might also consider using a Map instead of an object as the lookup: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Map
Recently, I have to face the same thing in which I need to search the string from a huge array.
After some search I found It'll be easy to handle with simple code:
Code:
var items = mydata.filter(function(item){
return item.word.toLowerCase().startsWith( 'gk );
})
See https://jsfiddle.net/maheshwaghmare/cfx3p40v/4/
Iterate over any item in the array. For every item you visit, check that item's id. If it's a match, return it.
If you just want teh codez:
function getId(array, id) {
for (var i = 0, len = array.length; i < len; i++) {
if (array[i].id === id) {
return array[i];
}
}
return null; // Nothing found
}
And the same thing using ECMAScript 5's Array methods:
function getId(array, id) {
var obj = array.filter(function (val) {
return val.id === id;
});
// Filter returns an array, and we just want the matching item.
return obj[0];
}
You may try out Sugarjs from http://sugarjs.com/.
It has a very sweet method on Arrays, .find. So you can find an element like this:
array.find( {id: 75} );
You may also pass an object with more properties to it to add another "where-clause".
Note that Sugarjs extends native objects, and some people consider this very evil...
As long as the browser supports ECMA-262, 5th edition (December 2009), this should work, almost one-liner:
var bFound = myArray.some(function (obj) {
return obj.id === 45;
});
Here's how I'd go about it in pure JavaScript, in the most minimal manner I can think of that works in ECMAScript 3 or later. It returns as soon as a match is found.
var getKeyValueById = function(array, key, id) {
var testArray = array.slice(), test;
while(test = testArray.pop()) {
if (test.id === id) {
return test[key];
}
}
// return undefined if no matching id is found in array
return;
}
var myArray = [{'id':'73', 'foo':'bar'}, {'id':'45', 'foo':'bar'}]
var result = getKeyValueById(myArray, 'foo', '45');
// result is 'bar', obtained from object with id of '45'
More generic and short
function findFromArray(array,key,value) {
return array.filter(function (element) {
return element[key] == value;
}).shift();
}
in your case Ex. var element = findFromArray(myArray,'id',45) that will give you the whole element.
We can use Jquery methods $.each()/$.grep()
var data= [];
$.each(array,function(i){if(n !== 5 && i > 4){data.push(item)}}
or
var data = $.grep(array, function( n, i ) {
return ( n !== 5 && i > 4 );
});
use ES6 syntax:
Array.find, Array.filter, Array.forEach, Array.map
Or use Lodash https://lodash.com/docs/4.17.10#filter, Underscore https://underscorejs.org/#filter
Building on the accepted answer:
jQuery:
var foo = $.grep(myArray, function(e){ return e.id === foo_id})
myArray.pop(foo)
Or CoffeeScript:
foo = $.grep myArray, (e) -> e.id == foo_id
myArray.pop foo
Use Array.prototype.filter() function.
DEMO: https://jsfiddle.net/sumitridhal/r0cz0w5o/4/
JSON
var jsonObj =[
{
"name": "Me",
"info": {
"age": "15",
"favColor": "Green",
"pets": true
}
},
{
"name": "Alex",
"info": {
"age": "16",
"favColor": "orange",
"pets": false
}
},
{
"name": "Kyle",
"info": {
"age": "15",
"favColor": "Blue",
"pets": false
}
}
];
FILTER
var getPerson = function(name){
return jsonObj.filter(function(obj) {
return obj.name === name;
});
}
You can do this even in pure JavaScript by using the in built "filter" function for arrays:
Array.prototype.filterObjects = function(key, value) {
return this.filter(function(x) { return x[key] === value; })
}
So now simply pass "id" in place of key and "45" in place of value, and you will get the full object matching an id of 45. So that would be,
myArr.filterObjects("id", "45");
I really liked the answer provided by Aaron Digulla but needed to keep my array of objects so I could iterate through it later. So I modified it to
var indexer = {};
for (var i = 0; i < array.length; i++) {
indexer[array[i].id] = parseInt(i);
}
//Then you can access object properties in your array using
array[indexer[id]].property
Use:
var retObj ={};
$.each(ArrayOfObjects, function (index, obj) {
if (obj.id === '5') { // id.toString() if it is int
retObj = obj;
return false;
}
});
return retObj;
It should return an object by id.
This solution may helpful as well:
Array.prototype.grep = function (key, value) {
var that = this, ret = [];
this.forEach(function (elem, index) {
if (elem[key] === value) {
ret.push(that[index]);
}
});
return ret.length < 2 ? ret[0] : ret;
};
var bar = myArray.grep("id","45");
I made it just like $.grep and if one object is find out, function will return the object, rather than an array.
Dynamic cached find
In this solution, when we search for some object, we save it in cache. This is middle point between "always search solutions" and "create hash-map for each object in precalculations".
let cachedFind = (()=>{
let cache = new Map();
return (arr,id,el=null) =>
cache.get(id) || (el=arr.find(o=> o.id==id), cache.set(id,el), el);
})();
// ---------
// TEST
// ---------
let myArray = [...Array(100000)].map((x,i)=> ({'id':`${i}`, 'foo':`bar_${i}`}));
// example usage
console.log( cachedFind(myArray,'1234').foo );
// Benchmark
let bench = (id) => {
console.time ('time for '+id );
console.log ( cachedFind(myArray,id).foo ); // FIND
console.timeEnd('time for '+id );
}
console.log('----- no cached -----');
bench(50000);
bench(79980);
bench(99990);
console.log('----- cached -----');
bench(79980); // cached
bench(99990); // cached

How to write a Ternary Operator for this operation? [duplicate]

I've got an array:
myArray = [{'id':'73','foo':'bar'},{'id':'45','foo':'bar'}, etc.]
I'm unable to change the structure of the array. I'm being passed an id of 45, and I want to get 'bar' for that object in the array.
How do I do this in JavaScript or using jQuery?
Use the find() method:
myArray.find(x => x.id === '45').foo;
From MDN:
The find() method returns the first value in the array, if an element in the array satisfies the provided testing function. Otherwise undefined is returned.
If you want to find its index instead, use findIndex():
myArray.findIndex(x => x.id === '45');
From MDN:
The findIndex() method returns the index of the first element in the array that satisfies the provided testing function. Otherwise -1 is returned.
If you want to get an array of matching elements, use the filter() method instead:
myArray.filter(x => x.id === '45');
This will return an array of objects. If you want to get an array of foo properties, you can do this with the map() method:
myArray.filter(x => x.id === '45').map(x => x.foo);
Side note: methods like find() or filter(), and arrow functions are not supported by older browsers (like IE), so if you want to support these browsers, you should transpile your code using Babel (with the polyfill).
As you are already using jQuery, you can use the grep function which is intended for searching an array:
var result = $.grep(myArray, function(e){ return e.id == id; });
The result is an array with the items found. If you know that the object is always there and that it only occurs once, you can just use result[0].foo to get the value. Otherwise you should check the length of the resulting array. Example:
if (result.length === 0) {
// no result found
} else if (result.length === 1) {
// property found, access the foo property using result[0].foo
} else {
// multiple items found
}
Another solution is to create a lookup object:
var lookup = {};
for (var i = 0, len = array.length; i < len; i++) {
lookup[array[i].id] = array[i];
}
... now you can use lookup[id]...
This is especially interesting if you need to do many lookups.
This won't need much more memory since the IDs and objects will be shared.
ECMAScript 2015 (JavaScript ES6) provides the find()
method on arrays:
var myArray = [
{id:1, name:"bob"},
{id:2, name:"dan"},
{id:3, name:"barb"},
]
// grab the Array item which matchs the id "2"
var item = myArray.find(item => item.id === 2);
// print
console.log(item.name);
It works without external libraries. But if you want older browser support you might want to include this polyfill.
Underscore.js has a nice method for that:
myArray = [{'id':'73','foo':'bar'},{'id':'45','foo':'bar'},etc.]
obj = _.find(myArray, function(obj) { return obj.id == '45' })
I think the easiest way would be the following, but it won't work on Internet Explorer 8 (or earlier):
var result = myArray.filter(function(v) {
return v.id === '45'; // Filter out the appropriate one
})[0].foo; // Get result and access the foo property
Try the following
function findById(source, id) {
for (var i = 0; i < source.length; i++) {
if (source[i].id === id) {
return source[i];
}
}
throw "Couldn't find object with id: " + id;
}
myArray.filter(function(a){ return a.id == some_id_you_want })[0]
A generic and more flexible version of the findById function above:
// array = [{key:value},{key:value}]
function objectFindByKey(array, key, value) {
for (var i = 0; i < array.length; i++) {
if (array[i][key] === value) {
return array[i];
}
}
return null;
}
var array = [{'id':'73','foo':'bar'},{'id':'45','foo':'bar'}];
var result_obj = objectFindByKey(array, 'id', '45');
Performance
Today 2020.06.20 I perform test on MacOs High Sierra on Chrome 81.0, Firefox 77.0 and Safari 13.1 for chosen solutions.
Conclusions for solutions which use precalculations
Solutions with precalculations (K,L) are (much much) faster than other solutions and will not be compared with them - probably they are use some special build-in browser optimisations
surprisingly on Chrome and Safari solution based on Map (K) are much faster than solution based on object {} (L)
surprisingly on Safari for small arrays solution based on object {} (L) is slower than traditional for (E)
surprisingly on Firefox for small arrays solution based on Map (K) is slower than traditional for (E)
Conclusions when searched objects ALWAYS exists
solution which use traditional for (E) is fastest for small arrays and fast for big arrays
solution using cache (J) is fastest for big arrays - surprisingly for small arrays is medium fast
solutions based on find (A) and findIndex (B) are fast for small arras and medium fast on big arrays
solution based on $.map (H) is slowest on small arrays
solution based on reduce (D) is slowest on big arrays
Conclusions when searched objects NEVER exists
solution based on traditional for (E) is fastest on small and big arrays (except Chrome-small arrays where it is second fast)
solution based on reduce (D) is slowest on big arrays
solution which use cache (J) is medium fast but can be speed up if we save in cache also keys which have null values (which was not done here because we want to avoid unlimited memory consumption in cache in case when many not existing keys will be searched)
Details
For solutions
without precalculations: A
B
C
D
E
F
G
H
I
J (the J solution use 'inner' cache and it speed depend on how often searched elements will repeat)
with precalculations
K
L
I perform four tests. In tests I want to find 5 objects in 10 loop iterations (the objects ID not change during iterations) - so I call tested method 50 times but only first 5 times have unique id values:
small array (10 elements) and searched object ALWAYS exists - you can perform it HERE
big array (10k elements) and searched object ALWAYS exist - you can perform it HERE
small array (10 elements) and searched object NEVER exists - you can perform it HERE
big array (10k elements) and searched object NEVER exists - you can perform it HERE
Tested codes are presented below
function A(arr, id) {
return arr.find(o=> o.id==id);
}
function B(arr, id) {
let idx= arr.findIndex(o=> o.id==id);
return arr[idx];
}
function C(arr, id) {
return arr.filter(o=> o.id==id)[0];
}
function D(arr, id) {
return arr.reduce((a, b) => (a.id==id && a) || (b.id == id && b));
}
function E(arr, id) {
for (var i = 0; i < arr.length; i++) if (arr[i].id==id) return arr[i];
return null;
}
function F(arr, id) {
var retObj ={};
$.each(arr, (index, obj) => {
if (obj.id == id) {
retObj = obj;
return false;
}
});
return retObj;
}
function G(arr, id) {
return $.grep(arr, e=> e.id == id )[0];
}
function H(arr, id) {
return $.map(myArray, function(val) {
return val.id == id ? val : null;
})[0];
}
function I(arr, id) {
return _.find(arr, o => o.id==id);
}
let J = (()=>{
let cache = new Map();
return function J(arr,id,el=null) {
return cache.get(id) || (el=arr.find(o=> o.id==id), cache.set(id,el), el);
}
})();
function K(arr, id) {
return mapK.get(id)
}
function L(arr, id) {
return mapL[id];
}
// -------------
// TEST
// -------------
console.log('Find id=5');
myArray = [...Array(10)].map((x,i)=> ({'id':`${i}`, 'foo':`bar_${i}`}));
const mapK = new Map( myArray.map(el => [el.id, el]) );
const mapL = {}; myArray.forEach(el => mapL[el.id]=el);
[A,B,C,D,E,F,G,H,I,J,K,L].forEach(f=> console.log(`${f.name}: ${JSON.stringify(f(myArray, '5'))}`));
console.log('Whole array',JSON.stringify(myArray));
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>
This snippet only presents tested codes
Example tests results for Chrome for small array where searched objects always exists
As others have pointed out, .find() is the way to go when looking for one object within your array. However, if your object cannot be found using this method, your program will crash:
const myArray = [{'id':'73','foo':'bar'},{'id':'45','foo':'bar'}];
const res = myArray.find(x => x.id === '100').foo; // Uh oh!
/*
Error:
"Uncaught TypeError: Cannot read property 'foo' of undefined"
or in newer chrome versions:
Uncaught TypeError: Cannot read properties of undefined (reading 'foo')
*/
This can be fixed by checking whether the result of .find() is defined before using .foo on it. Modern JS allows us to do this easily with optional chaining, returning undefined if the object cannot be found, rather than crashing your code:
const myArray = [{'id':'73','foo':'bar'},{'id':'45','foo':'bar'}];
const res = myArray.find(x => x.id === '100')?.foo; // No error!
console.log(res); // undefined when the object cannot be found
If you do this multiple times, you may set up a Map (ES6):
const map = new Map( myArray.map(el => [el.id, el]) );
Then you can simply do a O(1) lookup:
map.get(27).foo
You can get this easily using the map() function:
myArray = [{'id':'73','foo':'bar'},{'id':'45','foo':'bar'}];
var found = $.map(myArray, function(val) {
return val.id == 45 ? val.foo : null;
});
//found[0] == "bar";
Working example: http://jsfiddle.net/hunter/Pxaua/
Using native Array.reduce
var array = [ {'id':'73' ,'foo':'bar'} , {'id':'45' ,'foo':'bar'} , ];
var id = 73;
var found = array.reduce(function(a, b){
return (a.id==id && a) || (b.id == id && b)
});
returns the object element if found, otherwise false
You can use filters,
function getById(id, myArray) {
return myArray.filter(function(obj) {
if(obj.id == id) {
return obj
}
})[0]
}
get_my_obj = getById(73, myArray);
While there are many correct answers here, many of them do not address the fact that this is an unnecessarily expensive operation if done more than once. In an extreme case this could be the cause of real performance problems.
In the real world, if you are processing a lot of items and performance is a concern it's much faster to initially build a lookup:
var items = [{'id':'73','foo':'bar'},{'id':'45','foo':'bar'}];
var lookup = items.reduce((o,i)=>o[i.id]=o,{});
you can then get at items in fixed time like this :
var bar = o[id];
You might also consider using a Map instead of an object as the lookup: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Map
Recently, I have to face the same thing in which I need to search the string from a huge array.
After some search I found It'll be easy to handle with simple code:
Code:
var items = mydata.filter(function(item){
return item.word.toLowerCase().startsWith( 'gk );
})
See https://jsfiddle.net/maheshwaghmare/cfx3p40v/4/
Iterate over any item in the array. For every item you visit, check that item's id. If it's a match, return it.
If you just want teh codez:
function getId(array, id) {
for (var i = 0, len = array.length; i < len; i++) {
if (array[i].id === id) {
return array[i];
}
}
return null; // Nothing found
}
And the same thing using ECMAScript 5's Array methods:
function getId(array, id) {
var obj = array.filter(function (val) {
return val.id === id;
});
// Filter returns an array, and we just want the matching item.
return obj[0];
}
You may try out Sugarjs from http://sugarjs.com/.
It has a very sweet method on Arrays, .find. So you can find an element like this:
array.find( {id: 75} );
You may also pass an object with more properties to it to add another "where-clause".
Note that Sugarjs extends native objects, and some people consider this very evil...
As long as the browser supports ECMA-262, 5th edition (December 2009), this should work, almost one-liner:
var bFound = myArray.some(function (obj) {
return obj.id === 45;
});
Here's how I'd go about it in pure JavaScript, in the most minimal manner I can think of that works in ECMAScript 3 or later. It returns as soon as a match is found.
var getKeyValueById = function(array, key, id) {
var testArray = array.slice(), test;
while(test = testArray.pop()) {
if (test.id === id) {
return test[key];
}
}
// return undefined if no matching id is found in array
return;
}
var myArray = [{'id':'73', 'foo':'bar'}, {'id':'45', 'foo':'bar'}]
var result = getKeyValueById(myArray, 'foo', '45');
// result is 'bar', obtained from object with id of '45'
More generic and short
function findFromArray(array,key,value) {
return array.filter(function (element) {
return element[key] == value;
}).shift();
}
in your case Ex. var element = findFromArray(myArray,'id',45) that will give you the whole element.
We can use Jquery methods $.each()/$.grep()
var data= [];
$.each(array,function(i){if(n !== 5 && i > 4){data.push(item)}}
or
var data = $.grep(array, function( n, i ) {
return ( n !== 5 && i > 4 );
});
use ES6 syntax:
Array.find, Array.filter, Array.forEach, Array.map
Or use Lodash https://lodash.com/docs/4.17.10#filter, Underscore https://underscorejs.org/#filter
Building on the accepted answer:
jQuery:
var foo = $.grep(myArray, function(e){ return e.id === foo_id})
myArray.pop(foo)
Or CoffeeScript:
foo = $.grep myArray, (e) -> e.id == foo_id
myArray.pop foo
Use Array.prototype.filter() function.
DEMO: https://jsfiddle.net/sumitridhal/r0cz0w5o/4/
JSON
var jsonObj =[
{
"name": "Me",
"info": {
"age": "15",
"favColor": "Green",
"pets": true
}
},
{
"name": "Alex",
"info": {
"age": "16",
"favColor": "orange",
"pets": false
}
},
{
"name": "Kyle",
"info": {
"age": "15",
"favColor": "Blue",
"pets": false
}
}
];
FILTER
var getPerson = function(name){
return jsonObj.filter(function(obj) {
return obj.name === name;
});
}
You can do this even in pure JavaScript by using the in built "filter" function for arrays:
Array.prototype.filterObjects = function(key, value) {
return this.filter(function(x) { return x[key] === value; })
}
So now simply pass "id" in place of key and "45" in place of value, and you will get the full object matching an id of 45. So that would be,
myArr.filterObjects("id", "45");
I really liked the answer provided by Aaron Digulla but needed to keep my array of objects so I could iterate through it later. So I modified it to
var indexer = {};
for (var i = 0; i < array.length; i++) {
indexer[array[i].id] = parseInt(i);
}
//Then you can access object properties in your array using
array[indexer[id]].property
Use:
var retObj ={};
$.each(ArrayOfObjects, function (index, obj) {
if (obj.id === '5') { // id.toString() if it is int
retObj = obj;
return false;
}
});
return retObj;
It should return an object by id.
This solution may helpful as well:
Array.prototype.grep = function (key, value) {
var that = this, ret = [];
this.forEach(function (elem, index) {
if (elem[key] === value) {
ret.push(that[index]);
}
});
return ret.length < 2 ? ret[0] : ret;
};
var bar = myArray.grep("id","45");
I made it just like $.grep and if one object is find out, function will return the object, rather than an array.
Dynamic cached find
In this solution, when we search for some object, we save it in cache. This is middle point between "always search solutions" and "create hash-map for each object in precalculations".
let cachedFind = (()=>{
let cache = new Map();
return (arr,id,el=null) =>
cache.get(id) || (el=arr.find(o=> o.id==id), cache.set(id,el), el);
})();
// ---------
// TEST
// ---------
let myArray = [...Array(100000)].map((x,i)=> ({'id':`${i}`, 'foo':`bar_${i}`}));
// example usage
console.log( cachedFind(myArray,'1234').foo );
// Benchmark
let bench = (id) => {
console.time ('time for '+id );
console.log ( cachedFind(myArray,id).foo ); // FIND
console.timeEnd('time for '+id );
}
console.log('----- no cached -----');
bench(50000);
bench(79980);
bench(99990);
console.log('----- cached -----');
bench(79980); // cached
bench(99990); // cached

Merging Objects Using Vanilla JavaScript

I've been working on an event handler to sync inputs with the state and came up with this solution:
https://gist.github.com/jadeallencook/678869f988bd65378d819496c4343e78
The handler pushes the value to the end point using the string and merges the result with the current state but I have to use lodash to accomplish it, is there anyway to do it in vanilla?
A reduce will produce a generic solution:
const path = 'foo-bar-num';
const paths = path.split('-');
const object = {};
const value = 'Woo hoo!';
paths.reduce(([object, value], path, idx) => {
object[path] = idx === paths.length - 1 ? value : {};
return [object[path], value];
}, [object, value]);
console.log(object)
Alternatively you can try lodash set function to set value for deeply nested object.
import set from 'lodash/set'
let path = 'foo-num';
const paths = path.split('-').join('.')
const value = 'some value';
set(object, paths, value)
You could store the last key and reduce only the keys before. Ath the end store the previous value and assign the new value.
function setValue(object, keys, value) {
var last = keys.pop(),
temp = keys.reduce((o, k) => o[k] = o[k] || {}, object);
temp.original = !(last in temp) ? null : temp[last];
temp[last] = value;
}
const
object = {},
path = 'foo-bar-num',
value = 'Woo hoo!';
setValue(object, path.split('-'), value);
console.log(object);

Access sub-property with generic/dynamic property list [duplicate]

I have a bunch of object attributes coming in as dot-delimited strings like "availability_meta.supplier.price", and I need to assign a corresponding value to record['availability_meta']['supplier']['price'] and so on.
Not everything is 3 levels deep: many are only 1 level deep and many are deeper than 3 levels.
Is there a good way to assign this programmatically in Javascript? For example, I need:
["foo.bar.baz", 1] // --> record.foo.bar.baz = 1
["qux.qaz", "abc"] // --> record.qux.qaz = "abc"
["foshizzle", 200] // --> record.foshizzle = 200
I imagine I could hack something together, but I don't have any good algorithm in mind so would appreciate suggestions. I'm using lodash if that's helpful, and open to other libraries that may make quick work of this.
EDIT this is on the backend and run infrequently, so not necessary to optimize for size, speed, etc. In fact code readability would be a plus here for future devs.
EDIT 2 This is NOT the same as the referenced duplicate. Namely, I need to be able to do this assignment multiple times for the same object, and the "duplicate" answer will simply overwrite sub-keys each time. Please reopen!
You mentioned lodash in your question, so I thought I should add their easy object set() and get() functions. Just do something like:
_.set(record, 'availability_meta.supplier.price', 99);
You can read more about it here: https://lodash.com/docs#set
These functions let you do more complex things too, like specify array indexes, etc :)
Something to get you started:
function assignProperty(obj, path, value) {
var props = path.split(".")
, i = 0
, prop;
for(; i < props.length - 1; i++) {
prop = props[i];
obj = obj[prop];
}
obj[props[i]] = value;
}
Assuming:
var arr = ["foo.bar.baz", 1];
You'd call it using:
assignProperty(record, arr[0], arr[1]);
Example: http://jsfiddle.net/x49g5w8L/
What about this?
function convertDotPathToNestedObject(path, value) {
const [last, ...paths] = path.split('.').reverse();
return paths.reduce((acc, el) => ({ [el]: acc }), { [last]: value });
}
convertDotPathToNestedObject('foo.bar.x', 'FooBar')
// { foo: { bar: { x: 'FooBar' } } }
Just do
record['foo.bar.baz'] = 99;
But how would this work? It's strictly for the adventurous with a V8 environment (Chrome or Node harmony), using Object.observe. We observe the the object and capture the addition of new properties. When the "property" foo.bar.baz is added (via an assignment), we detect that this is a dotted property, and transform it into an assignment to record['foo']['bar.baz'] (creating record['foo'] if it does not exist), which in turn is transformed into an assignment to record['foo']['bar']['baz']. It goes like this:
function enable_dot_assignments(changes) {
// Iterate over changes
changes.forEach(function(change) {
// Deconstruct change record.
var object = change.object;
var type = change.type;
var name = change.name;
// Handle only 'add' type changes
if (type !== 'add') return;
// Break the property into segments, and get first one.
var segments = name.split('.');
var first_segment = segments.shift();
// Skip non-dotted property.
if (!segments.length) return;
// If the property doesn't exist, create it as object.
if (!(first_segment in object)) object[first_segment] = {};
var subobject = object[first_segment];
// Ensure subobject also enables dot assignments.
Object.observe(subobject, enable_dot_assignments);
// Set value on subobject using remainder of dot path.
subobject[segments.join('.')] = object[name];
// Make subobject assignments synchronous.
Object.deliverChangeRecords(enable_dot_assignments);
// We don't need the 'a.b' property on the object.
delete object[name];
});
}
Now you can just do
Object.observe(record, enable_dot_assignments);
record['foo.bar.baz'] = 99;
Beware, however, that such assignments will be asynchronous, which may or may not work for you. To solve this, call Object.deliverChangeRecords immediately after the assignment. Or, although not as syntactically pleasing, you could write a helper function, also setting up the observer:
function dot_assignment(object, path, value) {
Object.observe(object, enable_dot_assignments);
object[path] = value;
Object.deliverChangeRecords(enable_dot_assignments);
}
dot_assignment(record, 'foo.bar.baz', 99);
Something like this example perhaps. It will extend a supplied object or create one if it no object is supplied. It is destructive in nature, if you supply keys that already exist in the object, but you can change that if that is not what you want. Uses ECMA5.
/*global console */
/*members split, pop, reduce, trim, forEach, log, stringify */
(function () {
'use strict';
function isObject(arg) {
return arg && typeof arg === 'object';
}
function convertExtend(arr, obj) {
if (!isObject(obj)) {
obj = {};
}
var str = arr[0],
last = obj,
props,
valProp;
if (typeof str === 'string') {
props = str.split('.');
valProp = props.pop();
props.reduce(function (nest, prop) {
prop = prop.trim();
last = nest[prop];
if (!isObject(last)) {
nest[prop] = last = {};
}
return last;
}, obj);
last[valProp] = arr[1];
}
return obj;
}
var x = ['fum'],
y = [
['foo.bar.baz', 1],
['foo.bar.fum', new Date()],
['qux.qaz', 'abc'],
['foshizzle', 200]
],
z = ['qux.qux', null],
record = convertExtend(x);
y.forEach(function (yi) {
convertExtend(yi, record);
});
convertExtend(z, record);
document.body.textContent = JSON.stringify(record, function (key, value, Undefined) {
/*jslint unparam:true */
/*jshint unused:false */
if (value === Undefined) {
value = String(value);
}
return value;
});
}());
it's an old question, but if anyone still looking for a solution can try this
function restructureObject(object){
let result = {};
for(let key in object){
const splittedKeys = key.split('.');
if(splittedKeys.length === 1){
result[key] = object[key];
}
else if(splittedKeys.length > 2){
result = {...result, ...{[splittedKeys.splice(0,1)]: {}} ,...restructureObject({[splittedKeys.join('.')]: object[key]})}
}else{
result[splittedKeys[0]] = {[splittedKeys[1]]: object[key]}
}
}
return result
}

Check if object exist in an array [duplicate]

I've got an array:
myArray = [{'id':'73','foo':'bar'},{'id':'45','foo':'bar'}, etc.]
I'm unable to change the structure of the array. I'm being passed an id of 45, and I want to get 'bar' for that object in the array.
How do I do this in JavaScript or using jQuery?
Use the find() method:
myArray.find(x => x.id === '45').foo;
From MDN:
The find() method returns the first value in the array, if an element in the array satisfies the provided testing function. Otherwise undefined is returned.
If you want to find its index instead, use findIndex():
myArray.findIndex(x => x.id === '45');
From MDN:
The findIndex() method returns the index of the first element in the array that satisfies the provided testing function. Otherwise -1 is returned.
If you want to get an array of matching elements, use the filter() method instead:
myArray.filter(x => x.id === '45');
This will return an array of objects. If you want to get an array of foo properties, you can do this with the map() method:
myArray.filter(x => x.id === '45').map(x => x.foo);
Side note: methods like find() or filter(), and arrow functions are not supported by older browsers (like IE), so if you want to support these browsers, you should transpile your code using Babel (with the polyfill).
As you are already using jQuery, you can use the grep function which is intended for searching an array:
var result = $.grep(myArray, function(e){ return e.id == id; });
The result is an array with the items found. If you know that the object is always there and that it only occurs once, you can just use result[0].foo to get the value. Otherwise you should check the length of the resulting array. Example:
if (result.length === 0) {
// no result found
} else if (result.length === 1) {
// property found, access the foo property using result[0].foo
} else {
// multiple items found
}
Another solution is to create a lookup object:
var lookup = {};
for (var i = 0, len = array.length; i < len; i++) {
lookup[array[i].id] = array[i];
}
... now you can use lookup[id]...
This is especially interesting if you need to do many lookups.
This won't need much more memory since the IDs and objects will be shared.
ECMAScript 2015 (JavaScript ES6) provides the find()
method on arrays:
var myArray = [
{id:1, name:"bob"},
{id:2, name:"dan"},
{id:3, name:"barb"},
]
// grab the Array item which matchs the id "2"
var item = myArray.find(item => item.id === 2);
// print
console.log(item.name);
It works without external libraries. But if you want older browser support you might want to include this polyfill.
Underscore.js has a nice method for that:
myArray = [{'id':'73','foo':'bar'},{'id':'45','foo':'bar'},etc.]
obj = _.find(myArray, function(obj) { return obj.id == '45' })
I think the easiest way would be the following, but it won't work on Internet Explorer 8 (or earlier):
var result = myArray.filter(function(v) {
return v.id === '45'; // Filter out the appropriate one
})[0].foo; // Get result and access the foo property
Try the following
function findById(source, id) {
for (var i = 0; i < source.length; i++) {
if (source[i].id === id) {
return source[i];
}
}
throw "Couldn't find object with id: " + id;
}
myArray.filter(function(a){ return a.id == some_id_you_want })[0]
A generic and more flexible version of the findById function above:
// array = [{key:value},{key:value}]
function objectFindByKey(array, key, value) {
for (var i = 0; i < array.length; i++) {
if (array[i][key] === value) {
return array[i];
}
}
return null;
}
var array = [{'id':'73','foo':'bar'},{'id':'45','foo':'bar'}];
var result_obj = objectFindByKey(array, 'id', '45');
Performance
Today 2020.06.20 I perform test on MacOs High Sierra on Chrome 81.0, Firefox 77.0 and Safari 13.1 for chosen solutions.
Conclusions for solutions which use precalculations
Solutions with precalculations (K,L) are (much much) faster than other solutions and will not be compared with them - probably they are use some special build-in browser optimisations
surprisingly on Chrome and Safari solution based on Map (K) are much faster than solution based on object {} (L)
surprisingly on Safari for small arrays solution based on object {} (L) is slower than traditional for (E)
surprisingly on Firefox for small arrays solution based on Map (K) is slower than traditional for (E)
Conclusions when searched objects ALWAYS exists
solution which use traditional for (E) is fastest for small arrays and fast for big arrays
solution using cache (J) is fastest for big arrays - surprisingly for small arrays is medium fast
solutions based on find (A) and findIndex (B) are fast for small arras and medium fast on big arrays
solution based on $.map (H) is slowest on small arrays
solution based on reduce (D) is slowest on big arrays
Conclusions when searched objects NEVER exists
solution based on traditional for (E) is fastest on small and big arrays (except Chrome-small arrays where it is second fast)
solution based on reduce (D) is slowest on big arrays
solution which use cache (J) is medium fast but can be speed up if we save in cache also keys which have null values (which was not done here because we want to avoid unlimited memory consumption in cache in case when many not existing keys will be searched)
Details
For solutions
without precalculations: A
B
C
D
E
F
G
H
I
J (the J solution use 'inner' cache and it speed depend on how often searched elements will repeat)
with precalculations
K
L
I perform four tests. In tests I want to find 5 objects in 10 loop iterations (the objects ID not change during iterations) - so I call tested method 50 times but only first 5 times have unique id values:
small array (10 elements) and searched object ALWAYS exists - you can perform it HERE
big array (10k elements) and searched object ALWAYS exist - you can perform it HERE
small array (10 elements) and searched object NEVER exists - you can perform it HERE
big array (10k elements) and searched object NEVER exists - you can perform it HERE
Tested codes are presented below
function A(arr, id) {
return arr.find(o=> o.id==id);
}
function B(arr, id) {
let idx= arr.findIndex(o=> o.id==id);
return arr[idx];
}
function C(arr, id) {
return arr.filter(o=> o.id==id)[0];
}
function D(arr, id) {
return arr.reduce((a, b) => (a.id==id && a) || (b.id == id && b));
}
function E(arr, id) {
for (var i = 0; i < arr.length; i++) if (arr[i].id==id) return arr[i];
return null;
}
function F(arr, id) {
var retObj ={};
$.each(arr, (index, obj) => {
if (obj.id == id) {
retObj = obj;
return false;
}
});
return retObj;
}
function G(arr, id) {
return $.grep(arr, e=> e.id == id )[0];
}
function H(arr, id) {
return $.map(myArray, function(val) {
return val.id == id ? val : null;
})[0];
}
function I(arr, id) {
return _.find(arr, o => o.id==id);
}
let J = (()=>{
let cache = new Map();
return function J(arr,id,el=null) {
return cache.get(id) || (el=arr.find(o=> o.id==id), cache.set(id,el), el);
}
})();
function K(arr, id) {
return mapK.get(id)
}
function L(arr, id) {
return mapL[id];
}
// -------------
// TEST
// -------------
console.log('Find id=5');
myArray = [...Array(10)].map((x,i)=> ({'id':`${i}`, 'foo':`bar_${i}`}));
const mapK = new Map( myArray.map(el => [el.id, el]) );
const mapL = {}; myArray.forEach(el => mapL[el.id]=el);
[A,B,C,D,E,F,G,H,I,J,K,L].forEach(f=> console.log(`${f.name}: ${JSON.stringify(f(myArray, '5'))}`));
console.log('Whole array',JSON.stringify(myArray));
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>
This snippet only presents tested codes
Example tests results for Chrome for small array where searched objects always exists
As others have pointed out, .find() is the way to go when looking for one object within your array. However, if your object cannot be found using this method, your program will crash:
const myArray = [{'id':'73','foo':'bar'},{'id':'45','foo':'bar'}];
const res = myArray.find(x => x.id === '100').foo; // Uh oh!
/*
Error:
"Uncaught TypeError: Cannot read property 'foo' of undefined"
or in newer chrome versions:
Uncaught TypeError: Cannot read properties of undefined (reading 'foo')
*/
This can be fixed by checking whether the result of .find() is defined before using .foo on it. Modern JS allows us to do this easily with optional chaining, returning undefined if the object cannot be found, rather than crashing your code:
const myArray = [{'id':'73','foo':'bar'},{'id':'45','foo':'bar'}];
const res = myArray.find(x => x.id === '100')?.foo; // No error!
console.log(res); // undefined when the object cannot be found
If you do this multiple times, you may set up a Map (ES6):
const map = new Map( myArray.map(el => [el.id, el]) );
Then you can simply do a O(1) lookup:
map.get(27).foo
You can get this easily using the map() function:
myArray = [{'id':'73','foo':'bar'},{'id':'45','foo':'bar'}];
var found = $.map(myArray, function(val) {
return val.id == 45 ? val.foo : null;
});
//found[0] == "bar";
Working example: http://jsfiddle.net/hunter/Pxaua/
Using native Array.reduce
var array = [ {'id':'73' ,'foo':'bar'} , {'id':'45' ,'foo':'bar'} , ];
var id = 73;
var found = array.reduce(function(a, b){
return (a.id==id && a) || (b.id == id && b)
});
returns the object element if found, otherwise false
You can use filters,
function getById(id, myArray) {
return myArray.filter(function(obj) {
if(obj.id == id) {
return obj
}
})[0]
}
get_my_obj = getById(73, myArray);
While there are many correct answers here, many of them do not address the fact that this is an unnecessarily expensive operation if done more than once. In an extreme case this could be the cause of real performance problems.
In the real world, if you are processing a lot of items and performance is a concern it's much faster to initially build a lookup:
var items = [{'id':'73','foo':'bar'},{'id':'45','foo':'bar'}];
var lookup = items.reduce((o,i)=>o[i.id]=o,{});
you can then get at items in fixed time like this :
var bar = o[id];
You might also consider using a Map instead of an object as the lookup: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Map
Recently, I have to face the same thing in which I need to search the string from a huge array.
After some search I found It'll be easy to handle with simple code:
Code:
var items = mydata.filter(function(item){
return item.word.toLowerCase().startsWith( 'gk );
})
See https://jsfiddle.net/maheshwaghmare/cfx3p40v/4/
Iterate over any item in the array. For every item you visit, check that item's id. If it's a match, return it.
If you just want teh codez:
function getId(array, id) {
for (var i = 0, len = array.length; i < len; i++) {
if (array[i].id === id) {
return array[i];
}
}
return null; // Nothing found
}
And the same thing using ECMAScript 5's Array methods:
function getId(array, id) {
var obj = array.filter(function (val) {
return val.id === id;
});
// Filter returns an array, and we just want the matching item.
return obj[0];
}
You may try out Sugarjs from http://sugarjs.com/.
It has a very sweet method on Arrays, .find. So you can find an element like this:
array.find( {id: 75} );
You may also pass an object with more properties to it to add another "where-clause".
Note that Sugarjs extends native objects, and some people consider this very evil...
As long as the browser supports ECMA-262, 5th edition (December 2009), this should work, almost one-liner:
var bFound = myArray.some(function (obj) {
return obj.id === 45;
});
Here's how I'd go about it in pure JavaScript, in the most minimal manner I can think of that works in ECMAScript 3 or later. It returns as soon as a match is found.
var getKeyValueById = function(array, key, id) {
var testArray = array.slice(), test;
while(test = testArray.pop()) {
if (test.id === id) {
return test[key];
}
}
// return undefined if no matching id is found in array
return;
}
var myArray = [{'id':'73', 'foo':'bar'}, {'id':'45', 'foo':'bar'}]
var result = getKeyValueById(myArray, 'foo', '45');
// result is 'bar', obtained from object with id of '45'
More generic and short
function findFromArray(array,key,value) {
return array.filter(function (element) {
return element[key] == value;
}).shift();
}
in your case Ex. var element = findFromArray(myArray,'id',45) that will give you the whole element.
We can use Jquery methods $.each()/$.grep()
var data= [];
$.each(array,function(i){if(n !== 5 && i > 4){data.push(item)}}
or
var data = $.grep(array, function( n, i ) {
return ( n !== 5 && i > 4 );
});
use ES6 syntax:
Array.find, Array.filter, Array.forEach, Array.map
Or use Lodash https://lodash.com/docs/4.17.10#filter, Underscore https://underscorejs.org/#filter
Building on the accepted answer:
jQuery:
var foo = $.grep(myArray, function(e){ return e.id === foo_id})
myArray.pop(foo)
Or CoffeeScript:
foo = $.grep myArray, (e) -> e.id == foo_id
myArray.pop foo
Use Array.prototype.filter() function.
DEMO: https://jsfiddle.net/sumitridhal/r0cz0w5o/4/
JSON
var jsonObj =[
{
"name": "Me",
"info": {
"age": "15",
"favColor": "Green",
"pets": true
}
},
{
"name": "Alex",
"info": {
"age": "16",
"favColor": "orange",
"pets": false
}
},
{
"name": "Kyle",
"info": {
"age": "15",
"favColor": "Blue",
"pets": false
}
}
];
FILTER
var getPerson = function(name){
return jsonObj.filter(function(obj) {
return obj.name === name;
});
}
You can do this even in pure JavaScript by using the in built "filter" function for arrays:
Array.prototype.filterObjects = function(key, value) {
return this.filter(function(x) { return x[key] === value; })
}
So now simply pass "id" in place of key and "45" in place of value, and you will get the full object matching an id of 45. So that would be,
myArr.filterObjects("id", "45");
I really liked the answer provided by Aaron Digulla but needed to keep my array of objects so I could iterate through it later. So I modified it to
var indexer = {};
for (var i = 0; i < array.length; i++) {
indexer[array[i].id] = parseInt(i);
}
//Then you can access object properties in your array using
array[indexer[id]].property
Use:
var retObj ={};
$.each(ArrayOfObjects, function (index, obj) {
if (obj.id === '5') { // id.toString() if it is int
retObj = obj;
return false;
}
});
return retObj;
It should return an object by id.
This solution may helpful as well:
Array.prototype.grep = function (key, value) {
var that = this, ret = [];
this.forEach(function (elem, index) {
if (elem[key] === value) {
ret.push(that[index]);
}
});
return ret.length < 2 ? ret[0] : ret;
};
var bar = myArray.grep("id","45");
I made it just like $.grep and if one object is find out, function will return the object, rather than an array.
Dynamic cached find
In this solution, when we search for some object, we save it in cache. This is middle point between "always search solutions" and "create hash-map for each object in precalculations".
let cachedFind = (()=>{
let cache = new Map();
return (arr,id,el=null) =>
cache.get(id) || (el=arr.find(o=> o.id==id), cache.set(id,el), el);
})();
// ---------
// TEST
// ---------
let myArray = [...Array(100000)].map((x,i)=> ({'id':`${i}`, 'foo':`bar_${i}`}));
// example usage
console.log( cachedFind(myArray,'1234').foo );
// Benchmark
let bench = (id) => {
console.time ('time for '+id );
console.log ( cachedFind(myArray,id).foo ); // FIND
console.timeEnd('time for '+id );
}
console.log('----- no cached -----');
bench(50000);
bench(79980);
bench(99990);
console.log('----- cached -----');
bench(79980); // cached
bench(99990); // cached

Categories

Resources