How to update element inside List with ImmutableJS? - javascript

Here is what official docs said
updateIn(keyPath: Array<any>, updater: (value: any) => any): List<T>
updateIn(keyPath: Array<any>, notSetValue: any, updater: (value: any) => any): List<T>
updateIn(keyPath: Iterable<any, any>, updater: (value: any) => any): List<T>
updateIn(keyPath: Iterable<any, any>, notSetValue: any, updater: (value: any) => any): List<T>
There is no way normal web developer (not functional programmer) would understand that!
I have pretty simple (for non-functional approach) case.
var arr = [];
arr.push({id: 1, name: "first", count: 2});
arr.push({id: 2, name: "second", count: 1});
arr.push({id: 3, name: "third", count: 2});
arr.push({id: 4, name: "fourth", count: 1});
var list = Immutable.List.of(arr);
How can I update list where element with name third have its count set to 4?

The most appropriate case is to use both findIndex and update methods.
list = list.update(
list.findIndex(function(item) {
return item.get("name") === "third";
}), function(item) {
return item.set("count", 4);
}
);
P.S. It's not always possible to use Maps. E.g. if names are not unique and I want to update all items with the same names.

With .setIn() you can do the same:
let obj = fromJS({
elem: [
{id: 1, name: "first", count: 2},
{id: 2, name: "second", count: 1},
{id: 3, name: "third", count: 2},
{id: 4, name: "fourth", count: 1}
]
});
obj = obj.setIn(['elem', 3, 'count'], 4);
If we don’t know the index of the entry we want to update. It’s pretty easy to find it using .findIndex():
const indexOfListToUpdate = obj.get('elem').findIndex(listItem => {
return listItem.get('name') === 'third';
});
obj = obj.setIn(['elem', indexOfListingToUpdate, 'count'], 4);
Hope it helps!

var index = list.findIndex(item => item.name === "three")
list = list.setIn([index, "count"], 4)
Explanation
Updating Immutable.js collections always return new versions of those collections leaving the original unchanged. Because of that, we can't use JavaScript's list[2].count = 4 mutation syntax. Instead we need to call methods, much like we might do with Java collection classes.
Let's start with a simpler example: just the counts in a list.
var arr = [];
arr.push(2);
arr.push(1);
arr.push(2);
arr.push(1);
var counts = Immutable.List.of(arr);
Now if we wanted to update the 3rd item, a plain JS array might look like: counts[2] = 4. Since we can't use mutation, and need to call a method, instead we can use: counts.set(2, 4) - that means set the value 4 at the index 2.
Deep updates
The example you gave has nested data though. We can't just use set() on the initial collection.
Immutable.js collections have a family of methods with names ending with "In" which allow you to make deeper changes in a nested set. Most common updating methods have a related "In" method. For example for set there is setIn. Instead of accepting an index or a key as the first argument, these "In" methods accept a "key path". The key path is an array of indexes or keys that illustrates how to get to the value you wish to update.
In your example, you wanted to update the item in the list at index 2, and then the value at the key "count" within that item. So the key path would be [2, "count"]. The second parameter to the setIn method works just like set, it's the new value we want to put there, so:
list = list.setIn([2, "count"], 4)
Finding the right key path
Going one step further, you actually said you wanted to update the item where the name is "three" which is different than just the 3rd item. For example, maybe your list is not sorted, or perhaps there the item named "two" was removed earlier? That means first we need to make sure we actually know the correct key path! For this we can use the findIndex() method (which, by the way, works almost exactly like Array#findIndex).
Once we've found the index in the list which has the item we want to update, we can provide the key path to the value we wish to update:
var index = list.findIndex(item => item.name === "three")
list = list.setIn([index, "count"], 4)
NB: Set vs Update
The original question mentions the update methods rather than the set methods. I'll explain the second argument in that function (called updater), since it's different from set(). While the second argument to set() is the new value we want, the second argument to update() is a function which accepts the previous value and returns the new value we want. Then, updateIn() is the "In" variation of update() which accepts a key path.
Say for example we wanted a variation of your example that didn't just set the count to 4, but instead incremented the existing count, we could provide a function which adds one to the existing value:
var index = list.findIndex(item => item.name === "three")
list = list.updateIn([index, "count"], value => value + 1)

Here is what official docs said… updateIn
You don't need updateIn, which is for nested structures only. You are looking for the update method, which has a much simpler signature and documentation:
Returns a new List with an updated value at index with the return
value of calling updater with the existing value, or notSetValue if
index was not set.
update(index: number, updater: (value: T) => T): List<T>
update(index: number, notSetValue: T, updater: (value: T) => T): List<T>
which, as the Map::update docs suggest, is "equivalent to: list.set(index, updater(list.get(index, notSetValue)))".
where element with name "third"
That's not how lists work. You have to know the index of the element that you want to update, or you have to search for it.
How can I update list where element with name third have its count set to 4?
This should do it:
list = list.update(2, function(v) {
return {id: v.id, name: v.name, count: 4};
});

Use .map()
list = list.map(item =>
item.get("name") === "third" ? item.set("count", 4) : item
);
var arr = [];
arr.push({id: 1, name: "first", count: 2});
arr.push({id: 2, name: "second", count: 1});
arr.push({id: 3, name: "third", count: 2});
arr.push({id: 4, name: "fourth", count: 1});
var list = Immutable.fromJS(arr);
var newList = list.map(function(item) {
if(item.get("name") === "third") {
return item.set("count", 4);
} else {
return item;
}
});
console.log('newList', newList.toJS());
// More succinctly, using ES2015:
var newList2 = list.map(item =>
item.get("name") === "third" ? item.set("count", 4) : item
);
console.log('newList2', newList2.toJS());
<script src="https://cdnjs.cloudflare.com/ajax/libs/immutable/3.8.1/immutable.js"></script>

I really like this approach from the thomastuts website:
const book = fromJS({
title: 'Harry Potter & The Goblet of Fire',
isbn: '0439139600',
series: 'Harry Potter',
author: {
firstName: 'J.K.',
lastName: 'Rowling'
},
genres: [
'Crime',
'Fiction',
'Adventure',
],
storeListings: [
{storeId: 'amazon', price: 7.95},
{storeId: 'barnesnoble', price: 7.95},
{storeId: 'biblio', price: 4.99},
{storeId: 'bookdepository', price: 11.88},
]
});
const indexOfListingToUpdate = book.get('storeListings').findIndex(listing => {
return listing.get('storeId') === 'amazon';
});
const updatedBookState = book.setIn(['storeListings', indexOfListingToUpdate, 'price'], 6.80);
return state.set('book', updatedBookState);

You can use map:
list = list.map((item) => {
return item.get("name") === "third" ? item.set("count", 4) : item;
});
But this will iterate over the entire collection.

Related

store array of object key value inside separate array on the basis of index [duplicate]

I have JavaScript object array with the following structure:
objArray = [ { foo: 1, bar: 2}, { foo: 3, bar: 4}, { foo: 5, bar: 6} ];
I want to extract a field from each object, and get an array containing the values, for example field foo would give array [ 1, 3, 5 ].
I can do this with this trivial approach:
function getFields(input, field) {
var output = [];
for (var i=0; i < input.length ; ++i)
output.push(input[i][field]);
return output;
}
var result = getFields(objArray, "foo"); // returns [ 1, 3, 5 ]
Is there a more elegant or idiomatic way to do this, so that a custom utility function would be unnecessary?
Note about suggested duplicate, it covers how to convert a single object to an array.
Here is a shorter way of achieving it:
let result = objArray.map(a => a.foo);
OR
let result = objArray.map(({ foo }) => foo)
You can also check Array.prototype.map().
Yes, but it relies on an ES5 feature of JavaScript. This means it will not work in IE8 or older.
var result = objArray.map(function(a) {return a.foo;});
On ES6 compatible JS interpreters you can use an arrow function for brevity:
var result = objArray.map(a => a.foo);
Array.prototype.map documentation
Speaking for the JS only solutions, I've found that, inelegant as it may be, a simple indexed for loop is more performant than its alternatives.
Extracting single property from a 100000 element array (via jsPerf)
Traditional for loop 368 Ops/sec
var vals=[];
for(var i=0;i<testArray.length;i++){
vals.push(testArray[i].val);
}
ES6 for..of loop 303 Ops/sec
var vals=[];
for(var item of testArray){
vals.push(item.val);
}
Array.prototype.map 19 Ops/sec
var vals = testArray.map(function(a) {return a.val;});
TL;DR - .map() is slow, but feel free to use it if you feel readability is worth more than performance.
Edit #2: 6/2019 - jsPerf link broken, removed.
Check out Lodash's _.pluck() function or Underscore's _.pluck() function. Both do exactly what you want in a single function call!
var result = _.pluck(objArray, 'foo');
Update: _.pluck() has been removed as of Lodash v4.0.0, in favour of _.map() in combination with something similar to Niet's answer. _.pluck() is still available in Underscore.
Update 2: As Mark points out in the comments, somewhere between Lodash v4 and 4.3, a new function has been added that provides this functionality again. _.property() is a shorthand function that returns a function for getting the value of a property in an object.
Additionally, _.map() now allows a string to be passed in as the second parameter, which is passed into _.property(). As a result, the following two lines are equivalent to the code sample above from pre-Lodash 4.
var result = _.map(objArray, 'foo');
var result = _.map(objArray, _.property('foo'));
_.property(), and hence _.map(), also allow you to provide a dot-separated string or array in order to access sub-properties:
var objArray = [
{
someProperty: { aNumber: 5 }
},
{
someProperty: { aNumber: 2 }
},
{
someProperty: { aNumber: 9 }
}
];
var result = _.map(objArray, _.property('someProperty.aNumber'));
var result = _.map(objArray, _.property(['someProperty', 'aNumber']));
Both _.map() calls in the above example will return [5, 2, 9].
If you're a little more into functional programming, take a look at Ramda's R.pluck() function, which would look something like this:
var result = R.pluck('foo')(objArray); // or just R.pluck('foo', objArray)
Example to collect the different fields from the object array
let inputArray = [
{ id: 1, name: "name1", value: "value1" },
{ id: 2, name: "name2", value: "value2" },
];
let ids = inputArray.map( (item) => item.id);
let names = inputArray.map((item) => item.name);
let values = inputArray.map((item) => item.value);
console.log(ids);
console.log(names);
console.log(values);
Result :
[ 1, 2 ]
[ 'name1', 'name2' ]
[ 'value1', 'value2' ]
It is better to use some sort of libraries like lodash or underscore for cross browser assurance.
In Lodash you can get values of a property in array by following method
_.map(objArray,"foo")
and in Underscore
_.pluck(objArray,"foo")
Both will return
[1, 2, 3]
Using Array.prototype.map:
function getFields(input, field) {
return input.map(function(o) {
return o[field];
});
}
See the above link for a shim for pre-ES5 browsers.
In ES6, you can do:
const objArray = [{foo: 1, bar: 2}, {foo: 3, bar: 4}, {foo: 5, bar: 6}]
objArray.map(({ foo }) => foo)
If you want multiple values in ES6+ the following will work
objArray = [ { foo: 1, bar: 2, baz: 9}, { foo: 3, bar: 4, baz: 10}, { foo: 5, bar: 6, baz: 20} ];
let result = objArray.map(({ foo, baz }) => ({ foo, baz }))
This works as {foo, baz} on the left is using object destructoring and on the right side of the arrow is equivalent to {foo: foo, baz: baz} due to ES6's enhanced object literals.
While map is a proper solution to select 'columns' from a list of objects, it has a downside. If not explicitly checked whether or not the columns exists, it'll throw an error and (at best) provide you with undefined.
I'd opt for a reduce solution, which can simply ignore the property or even set you up with a default value.
function getFields(list, field) {
// reduce the provided list to an array only containing the requested field
return list.reduce(function(carry, item) {
// check if the item is actually an object and does contain the field
if (typeof item === 'object' && field in item) {
carry.push(item[field]);
}
// return the 'carry' (which is the list of matched field values)
return carry;
}, []);
}
jsbin example
This would work even if one of the items in the provided list is not an object or does not contain the field.
It can even be made more flexible by negotiating a default value should an item not be an object or not contain the field.
function getFields(list, field, otherwise) {
// reduce the provided list to an array containing either the requested field or the alternative value
return list.reduce(function(carry, item) {
// If item is an object and contains the field, add its value and the value of otherwise if not
carry.push(typeof item === 'object' && field in item ? item[field] : otherwise);
// return the 'carry' (which is the list of matched field values)
return carry;
}, []);
}
jsbin example
This would be the same with map, as the length of the returned array would be the same as the provided array. (In which case a map is slightly cheaper than a reduce):
function getFields(list, field, otherwise) {
// map the provided list to an array containing either the requested field or the alternative value
return list.map(function(item) {
// If item is an object and contains the field, add its value and the value of otherwise if not
return typeof item === 'object' && field in item ? item[field] : otherwise;
}, []);
}
jsbin example
And then there is the most flexible solution, one which lets you switch between both behaviours simply by providing an alternative value.
function getFields(list, field, otherwise) {
// determine once whether or not to use the 'otherwise'
var alt = typeof otherwise !== 'undefined';
// reduce the provided list to an array only containing the requested field
return list.reduce(function(carry, item) {
// If item is an object and contains the field, add its value and the value of 'otherwise' if it was provided
if (typeof item === 'object' && field in item) {
carry.push(item[field]);
}
else if (alt) {
carry.push(otherwise);
}
// return the 'carry' (which is the list of matched field values)
return carry;
}, []);
}
jsbin example
As the examples above (hopefully) shed some light on the way this works, lets shorten the function a bit by utilising the Array.concat function.
function getFields(list, field, otherwise) {
var alt = typeof otherwise !== 'undefined';
return list.reduce(function(carry, item) {
return carry.concat(typeof item === 'object' && field in item ? item[field] : (alt ? otherwise : []));
}, []);
}
jsbin example
The above answer is good for a single property but when select multiple properties from an array use this
var arrayObj=[{Name,'A',Age:20,Email:'a.gmail.com'},{Name,'B',Age:30,Email:'b.gmail.com'},{Name,'C',Age:40,Email:'c.gmail.com'}]
now I select only two fields
var outPutArray=arrayObj.map(( {Name,Email} ) => ({Name,Email}) )
console.log(outPutArray)
If you want to also support array-like objects, use Array.from (ES2015):
Array.from(arrayLike, x => x.foo);
The advantage it has over Array.prototype.map() method is the input can also be a Set:
let arrayLike = new Set([{foo: 1}, {foo: 2}, {foo: 3}]);
In general, if you want to extrapolate object values which are inside an array (like described in the question) then you could use reduce, map and array destructuring.
ES6
let a = [{ z: 'word', c: 'again', d: 'some' }, { u: '1', r: '2', i: '3' }];
let b = a.reduce((acc, obj) => [...acc, Object.values(obj).map(y => y)], []);
console.log(b)
The equivalent using for in loop would be:
for (let i in a) {
let temp = [];
for (let j in a[i]) {
temp.push(a[i][j]);
}
array.push(temp);
}
Produced output: ["word", "again", "some", "1", "2", "3"]
If you have nested arrays you can make it work like this:
const objArray = [
{ id: 1, items: { foo:4, bar: 2}},
{ id: 2, items: { foo:3, bar: 2}},
{ id: 3, items: { foo:1, bar: 2}}
];
let result = objArray.map(({id, items: {foo}}) => ({id, foo}))
console.log(result)
Easily extracting multiple properties from array of objects:
let arrayOfObjects = [
{id:1, name:'one', desc:'something'},
{id:2, name:'two', desc:'something else'}
];
//below will extract just the id and name
let result = arrayOfObjects.map(({id, name}) => ({id, name}));
result will be [{id:1, name:'one'},{id:2, name:'two'}]
Add or remove properties as needed in the map function
In ES6, in case you want to dynamically pass the field as a string:
function getFields(array, field) {
return array.map(a => a[field]);
}
let result = getFields(array, 'foo');
It depends on your definition of "better".
The other answers point out the use of map, which is natural (especially for guys used to functional style) and concise. I strongly recommend using it (if you don't bother with the few IE8- IT guys). So if "better" means "more concise", "maintainable", "understandable" then yes, it's way better.
On the other hand, this beauty doesn't come without additional costs. I'm not a big fan of microbench, but I've put up a small test here. The results are predictable, the old ugly way seems to be faster than the map function. So if "better" means "faster", then no, stay with the old school fashion.
Again this is just a microbench and in no way advocating against the use of map, it's just my two cents :).
create an empty array then forEach element from your list, push what you want from that object into your empty array.
let objArray2 = [];
objArray.forEach(arr => objArray2.push(arr.foo));
From an array of objects, extract the value of a property as an array with for loop.
//input
objArray = [ { foo: 1, bar: 2}, { foo: 3, bar: 4}, { foo: 5, bar: 6} ];
//Code
let output=[];
for(let item of objArray){
output.push(item.foo);
}
// Output
[ 1, 3, 5 ]
Above provided answer is good for extracting single property, what if you want to extract more than one property from array of objects.
Here is the solution!!
In case of that we can simply use _.pick(object, [paths])
_.pick(object, [paths])
Lets assume objArray has objects with three properties like below
objArray = [ { foo: 1, bar: 2, car:10}, { foo: 3, bar: 4, car:10}, { foo: 5, bar: 6, car:10} ];
Now we want to extract foo and bar property from every object and store them in a separate array.
First we will iterate array elements using map and then we apply Lodash Library Standard _.pick() method on it.
Now we are able to extract 'foo' and 'bar' property.
var newArray = objArray.map((element)=>{ return _.pick(element, ['foo','bar'])})
console.log(newArray);
and result would be
[{foo: 1, bar: 2},{foo: 3, bar: 4},{foo: 5, bar: 6}]
enjoy!!!
Here is another shape of using map method on array of objects to get back specific property:
const objArray = [ { foo: 1, bar: 2}, { foo: 3, bar: 4}, { foo: 5, bar: 6} ];
const getProp = prop => obj => obj[prop];
const getFoo = getProp('foo');
const fooes = objArray.map(getFoo);
console.log(fooes);
I would only improve one of the answers if you even don't know the exact property of the object you'r playing with use below:
let result = objArray.map(a => a[Object.getOwnPropertyNames(a)]);
Function map is a good choice when dealing with object arrays. Although there have been a number of good answers posted already, the example of using map with combination with filter might be helpful.
In case you want to exclude the properties which values are undefined or exclude just a specific property, you could do the following:
var obj = {value1: "val1", value2: "val2", Ndb_No: "testing", myVal: undefined};
var keysFiltered = Object.keys(obj).filter(function(item){return !(item == "Ndb_No" || obj[item] == undefined)});
var valuesFiltered = keysFiltered.map(function(item) {return obj[item]});
https://jsfiddle.net/ohea7mgk/
Destructure and get specific attributes from array of object:
const customerList = dealerUserData?.partyDetails.map(
({ partyId, custAccountId }) => ({
partyId,
custAccountId,
customerId: dealerUserData?._id,
userId: dealerUserData?.authUserID,
}),
);

Why doesn't reassigning the parameter element in forEach work

For the following code block:
const items = [
{ id: 1, name: 'one' },
{ id: 2, name: 'two' },
];
const changes = {
name: 'hello'
}
items.forEach((item, i) => {
item = {
...item,
...changes
}
})
console.log(items) // items NOT reassigned with changes
items.forEach((item, i) => {
items[i] = {
...item,
...changes
}
});
console.log(items) // items reassigned with changes
Why does reassigning the values right on the element iteration not change the objects in the array?
item = {
...item,
...changes
}
but changing it by accessing it with the index does change the objects in the array?
items2[i] = {
...item,
...changes
}
And what is the best way to update objects in an array? Is items2[i] ideal?
Say no to param reassign!
This is a sort of a fundamental understanding of higher level languages like JavaScript.
Function parameters are temporary containers of a given value.
Hence any "reassigning" will not change the original value.
For example look at the example below.
let importantObject = {
hello: "world"
}
// We are just reassigning the function parameter
function tryUpdateObjectByParamReassign(parameter) {
parameter = {
...parameter,
updated: "object"
}
}
tryUpdateObjectByParamReassign(importantObject)
console.log("When tryUpdateObjectByParamReassign the object is not updated");
console.log(importantObject);
As you can see when you re-assign a parameter the original value will not be touched. There is even a nice Lint rule since this is a heavily bug prone area.
Mutation will work here, but ....
However if you "mutate" the variable this will work.
let importantObject = {
hello: "world"
}
// When we mutate the returned object since we are mutating the object the updates will be shown
function tryUpdateObjectByObjectMutation(parameter) {
parameter["updated"] = "object"
}
tryUpdateObjectByObjectMutation(importantObject)
console.log("When tryUpdateObjectByObjectMutation the object is updated");
console.log(importantObject);
So coming back to your code snippet. In a foreach loop what happens is a "function call" per each array item where the array item is passed in as a parameter. So similar to above what will work here is as mutation.
const items = [
{ id: 1, name: 'one' },
{ id: 2, name: 'two' },
];
const changes = {
name: 'hello'
}
items.forEach((item, i) => {
// Object assign just copies an object into another object
Object.assign(item, changes);
})
console.log(items)
But, it's better to avoid mutation!
It's better not mutate since this can lead to even more bugs. A better approach would be to use map and get a brand new collection of objects.
const items = [{
id: 1,
name: 'one'
},
{
id: 2,
name: 'two'
},
];
const changes = {
name: 'hello'
}
const updatedItems = items.map((item, i) => {
return {
...item,
...changes
}
})
console.log({
items
})
console.log({
updatedItems
})
As the MDN page for forEach says:
forEach() executes the callbackFn function once for each array
element; unlike map() or reduce() it always returns the value
undefined and is not chainable. The typical use case is to execute
side effects at the end of a chain.
Have a look here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
This means that although you did create new object for item, it was not returned as a value for that index of array. Unlike your second example, the first one is not changing original array, but just creates new objects and returns undefined. This is why your array is not modified.
I'd go with a classic Object.assign for this:
const items = [
{ id: 1, name: 'one' },
{ id: 2, name: 'two' },
];
const changes = {
name: 'hello'
}
items.forEach( (item) => Object.assign(item,changes) )
console.log(items)
Properties in the target object are overwritten by properties in the sources if they have the same key. Later sources' properties overwrite earlier ones.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
The other approach you can take is to use map and create a new array based on the original data and the changes:
const items = [
{ id: 1, name: 'one' },
{ id: 2, name: 'two' },
];
const changes = {
name: 'hello'
}
const newItems = items.map((item) => {
...item,
...changes
})
console.log(newItems);
But if you need to modify the original array, it's either accessing the elements by index, or Object.assign. Attempting to assign the value directly using the = operator doesn't work because the item argument is passed to the callback by value not by reference - you're not updating the object the array is pointing at.

How to add a extra property to array with typescript

I want to add an extra property to an array, but I get trouble with declaring it.
small part
const arr: string[] & {key: number} = ['123'];//ts error Property 'key' is missing
arr.key = 10;
large part
I need to group arr by index then sort it by value
add an index to array property will be a convenience if using Object.values but not Object.entries.
I know there are many approaches to do this, but I think it's much easier if I am using plain js
type Arr = {index: number, value: number};
const arr:Arr[] = []
arr.push({
index: 10,
value: 2
})
arr.push({
index: 10,
value: 1
})
arr.push({
index: 10,
value: 3
});
arr.push({
index: 20,
value: 100
});
arr.push({
index: 20,
value: 50
});
arr.reduce<Record<string, Arr[] & {index: number}>>((prev, curr) => {
if(!prev[curr.index]){
prev[curr.index] = [];//ts error Property missing
prev[curr.index].index = curr.index;
}
prev[curr.index].push(curr);
prev[curr.index].sort((a,b) => a.value - b.value);//or some insertion sort algorithm.
return prev;
},{})
An array literal can't by itself satisfy the intersection, the simplest way you could do it is to use Object.assign which returns an intersection of its arguments:
const arr: string[] & {key: number} = Object.assign(['123'], {
key: 10
});
And in your bigger example, the same could be used:
arr.reduce<Record<string, Arr[] & {index: number}>>((prev, curr) => {
if(!prev[curr.index]){
prev[curr.index] = Object.assign([], {
index: curr.index
})
}
prev[curr.index].push(curr);
prev[curr.index].sort((a,b) => a.value - b.value);//or some insertion sort algorithm.
return prev;
},{})
playground link
In your small part example, the type declaration string[] & {key: number} can never be satisfied because an array of strings cannot be the same as an object with key property.
Concerning your large part example, what exactly are you trying to achieve? If you say you want to group an array by an index, and then sort it by value, do you mean that the index is kind of your primary sort criterion and value the secondary criterion? In that case, I'd suppose you write your own sorting function that takes both values into account. You'll find an example in this Stackoverflow thread.

How do I a specific element from an Array? [duplicate]

I have JavaScript object array with the following structure:
objArray = [ { foo: 1, bar: 2}, { foo: 3, bar: 4}, { foo: 5, bar: 6} ];
I want to extract a field from each object, and get an array containing the values, for example field foo would give array [ 1, 3, 5 ].
I can do this with this trivial approach:
function getFields(input, field) {
var output = [];
for (var i=0; i < input.length ; ++i)
output.push(input[i][field]);
return output;
}
var result = getFields(objArray, "foo"); // returns [ 1, 3, 5 ]
Is there a more elegant or idiomatic way to do this, so that a custom utility function would be unnecessary?
Note about suggested duplicate, it covers how to convert a single object to an array.
Here is a shorter way of achieving it:
let result = objArray.map(a => a.foo);
OR
let result = objArray.map(({ foo }) => foo)
You can also check Array.prototype.map().
Yes, but it relies on an ES5 feature of JavaScript. This means it will not work in IE8 or older.
var result = objArray.map(function(a) {return a.foo;});
On ES6 compatible JS interpreters you can use an arrow function for brevity:
var result = objArray.map(a => a.foo);
Array.prototype.map documentation
Speaking for the JS only solutions, I've found that, inelegant as it may be, a simple indexed for loop is more performant than its alternatives.
Extracting single property from a 100000 element array (via jsPerf)
Traditional for loop 368 Ops/sec
var vals=[];
for(var i=0;i<testArray.length;i++){
vals.push(testArray[i].val);
}
ES6 for..of loop 303 Ops/sec
var vals=[];
for(var item of testArray){
vals.push(item.val);
}
Array.prototype.map 19 Ops/sec
var vals = testArray.map(function(a) {return a.val;});
TL;DR - .map() is slow, but feel free to use it if you feel readability is worth more than performance.
Edit #2: 6/2019 - jsPerf link broken, removed.
Check out Lodash's _.pluck() function or Underscore's _.pluck() function. Both do exactly what you want in a single function call!
var result = _.pluck(objArray, 'foo');
Update: _.pluck() has been removed as of Lodash v4.0.0, in favour of _.map() in combination with something similar to Niet's answer. _.pluck() is still available in Underscore.
Update 2: As Mark points out in the comments, somewhere between Lodash v4 and 4.3, a new function has been added that provides this functionality again. _.property() is a shorthand function that returns a function for getting the value of a property in an object.
Additionally, _.map() now allows a string to be passed in as the second parameter, which is passed into _.property(). As a result, the following two lines are equivalent to the code sample above from pre-Lodash 4.
var result = _.map(objArray, 'foo');
var result = _.map(objArray, _.property('foo'));
_.property(), and hence _.map(), also allow you to provide a dot-separated string or array in order to access sub-properties:
var objArray = [
{
someProperty: { aNumber: 5 }
},
{
someProperty: { aNumber: 2 }
},
{
someProperty: { aNumber: 9 }
}
];
var result = _.map(objArray, _.property('someProperty.aNumber'));
var result = _.map(objArray, _.property(['someProperty', 'aNumber']));
Both _.map() calls in the above example will return [5, 2, 9].
If you're a little more into functional programming, take a look at Ramda's R.pluck() function, which would look something like this:
var result = R.pluck('foo')(objArray); // or just R.pluck('foo', objArray)
Example to collect the different fields from the object array
let inputArray = [
{ id: 1, name: "name1", value: "value1" },
{ id: 2, name: "name2", value: "value2" },
];
let ids = inputArray.map( (item) => item.id);
let names = inputArray.map((item) => item.name);
let values = inputArray.map((item) => item.value);
console.log(ids);
console.log(names);
console.log(values);
Result :
[ 1, 2 ]
[ 'name1', 'name2' ]
[ 'value1', 'value2' ]
It is better to use some sort of libraries like lodash or underscore for cross browser assurance.
In Lodash you can get values of a property in array by following method
_.map(objArray,"foo")
and in Underscore
_.pluck(objArray,"foo")
Both will return
[1, 2, 3]
Using Array.prototype.map:
function getFields(input, field) {
return input.map(function(o) {
return o[field];
});
}
See the above link for a shim for pre-ES5 browsers.
In ES6, you can do:
const objArray = [{foo: 1, bar: 2}, {foo: 3, bar: 4}, {foo: 5, bar: 6}]
objArray.map(({ foo }) => foo)
If you want multiple values in ES6+ the following will work
objArray = [ { foo: 1, bar: 2, baz: 9}, { foo: 3, bar: 4, baz: 10}, { foo: 5, bar: 6, baz: 20} ];
let result = objArray.map(({ foo, baz }) => ({ foo, baz }))
This works as {foo, baz} on the left is using object destructoring and on the right side of the arrow is equivalent to {foo: foo, baz: baz} due to ES6's enhanced object literals.
While map is a proper solution to select 'columns' from a list of objects, it has a downside. If not explicitly checked whether or not the columns exists, it'll throw an error and (at best) provide you with undefined.
I'd opt for a reduce solution, which can simply ignore the property or even set you up with a default value.
function getFields(list, field) {
// reduce the provided list to an array only containing the requested field
return list.reduce(function(carry, item) {
// check if the item is actually an object and does contain the field
if (typeof item === 'object' && field in item) {
carry.push(item[field]);
}
// return the 'carry' (which is the list of matched field values)
return carry;
}, []);
}
jsbin example
This would work even if one of the items in the provided list is not an object or does not contain the field.
It can even be made more flexible by negotiating a default value should an item not be an object or not contain the field.
function getFields(list, field, otherwise) {
// reduce the provided list to an array containing either the requested field or the alternative value
return list.reduce(function(carry, item) {
// If item is an object and contains the field, add its value and the value of otherwise if not
carry.push(typeof item === 'object' && field in item ? item[field] : otherwise);
// return the 'carry' (which is the list of matched field values)
return carry;
}, []);
}
jsbin example
This would be the same with map, as the length of the returned array would be the same as the provided array. (In which case a map is slightly cheaper than a reduce):
function getFields(list, field, otherwise) {
// map the provided list to an array containing either the requested field or the alternative value
return list.map(function(item) {
// If item is an object and contains the field, add its value and the value of otherwise if not
return typeof item === 'object' && field in item ? item[field] : otherwise;
}, []);
}
jsbin example
And then there is the most flexible solution, one which lets you switch between both behaviours simply by providing an alternative value.
function getFields(list, field, otherwise) {
// determine once whether or not to use the 'otherwise'
var alt = typeof otherwise !== 'undefined';
// reduce the provided list to an array only containing the requested field
return list.reduce(function(carry, item) {
// If item is an object and contains the field, add its value and the value of 'otherwise' if it was provided
if (typeof item === 'object' && field in item) {
carry.push(item[field]);
}
else if (alt) {
carry.push(otherwise);
}
// return the 'carry' (which is the list of matched field values)
return carry;
}, []);
}
jsbin example
As the examples above (hopefully) shed some light on the way this works, lets shorten the function a bit by utilising the Array.concat function.
function getFields(list, field, otherwise) {
var alt = typeof otherwise !== 'undefined';
return list.reduce(function(carry, item) {
return carry.concat(typeof item === 'object' && field in item ? item[field] : (alt ? otherwise : []));
}, []);
}
jsbin example
The above answer is good for a single property but when select multiple properties from an array use this
var arrayObj=[{Name,'A',Age:20,Email:'a.gmail.com'},{Name,'B',Age:30,Email:'b.gmail.com'},{Name,'C',Age:40,Email:'c.gmail.com'}]
now I select only two fields
var outPutArray=arrayObj.map(( {Name,Email} ) => ({Name,Email}) )
console.log(outPutArray)
If you want to also support array-like objects, use Array.from (ES2015):
Array.from(arrayLike, x => x.foo);
The advantage it has over Array.prototype.map() method is the input can also be a Set:
let arrayLike = new Set([{foo: 1}, {foo: 2}, {foo: 3}]);
In general, if you want to extrapolate object values which are inside an array (like described in the question) then you could use reduce, map and array destructuring.
ES6
let a = [{ z: 'word', c: 'again', d: 'some' }, { u: '1', r: '2', i: '3' }];
let b = a.reduce((acc, obj) => [...acc, Object.values(obj).map(y => y)], []);
console.log(b)
The equivalent using for in loop would be:
for (let i in a) {
let temp = [];
for (let j in a[i]) {
temp.push(a[i][j]);
}
array.push(temp);
}
Produced output: ["word", "again", "some", "1", "2", "3"]
If you have nested arrays you can make it work like this:
const objArray = [
{ id: 1, items: { foo:4, bar: 2}},
{ id: 2, items: { foo:3, bar: 2}},
{ id: 3, items: { foo:1, bar: 2}}
];
let result = objArray.map(({id, items: {foo}}) => ({id, foo}))
console.log(result)
Easily extracting multiple properties from array of objects:
let arrayOfObjects = [
{id:1, name:'one', desc:'something'},
{id:2, name:'two', desc:'something else'}
];
//below will extract just the id and name
let result = arrayOfObjects.map(({id, name}) => ({id, name}));
result will be [{id:1, name:'one'},{id:2, name:'two'}]
Add or remove properties as needed in the map function
In ES6, in case you want to dynamically pass the field as a string:
function getFields(array, field) {
return array.map(a => a[field]);
}
let result = getFields(array, 'foo');
It depends on your definition of "better".
The other answers point out the use of map, which is natural (especially for guys used to functional style) and concise. I strongly recommend using it (if you don't bother with the few IE8- IT guys). So if "better" means "more concise", "maintainable", "understandable" then yes, it's way better.
On the other hand, this beauty doesn't come without additional costs. I'm not a big fan of microbench, but I've put up a small test here. The results are predictable, the old ugly way seems to be faster than the map function. So if "better" means "faster", then no, stay with the old school fashion.
Again this is just a microbench and in no way advocating against the use of map, it's just my two cents :).
create an empty array then forEach element from your list, push what you want from that object into your empty array.
let objArray2 = [];
objArray.forEach(arr => objArray2.push(arr.foo));
From an array of objects, extract the value of a property as an array with for loop.
//input
objArray = [ { foo: 1, bar: 2}, { foo: 3, bar: 4}, { foo: 5, bar: 6} ];
//Code
let output=[];
for(let item of objArray){
output.push(item.foo);
}
// Output
[ 1, 3, 5 ]
Above provided answer is good for extracting single property, what if you want to extract more than one property from array of objects.
Here is the solution!!
In case of that we can simply use _.pick(object, [paths])
_.pick(object, [paths])
Lets assume objArray has objects with three properties like below
objArray = [ { foo: 1, bar: 2, car:10}, { foo: 3, bar: 4, car:10}, { foo: 5, bar: 6, car:10} ];
Now we want to extract foo and bar property from every object and store them in a separate array.
First we will iterate array elements using map and then we apply Lodash Library Standard _.pick() method on it.
Now we are able to extract 'foo' and 'bar' property.
var newArray = objArray.map((element)=>{ return _.pick(element, ['foo','bar'])})
console.log(newArray);
and result would be
[{foo: 1, bar: 2},{foo: 3, bar: 4},{foo: 5, bar: 6}]
enjoy!!!
Here is another shape of using map method on array of objects to get back specific property:
const objArray = [ { foo: 1, bar: 2}, { foo: 3, bar: 4}, { foo: 5, bar: 6} ];
const getProp = prop => obj => obj[prop];
const getFoo = getProp('foo');
const fooes = objArray.map(getFoo);
console.log(fooes);
I would only improve one of the answers if you even don't know the exact property of the object you'r playing with use below:
let result = objArray.map(a => a[Object.getOwnPropertyNames(a)]);
Function map is a good choice when dealing with object arrays. Although there have been a number of good answers posted already, the example of using map with combination with filter might be helpful.
In case you want to exclude the properties which values are undefined or exclude just a specific property, you could do the following:
var obj = {value1: "val1", value2: "val2", Ndb_No: "testing", myVal: undefined};
var keysFiltered = Object.keys(obj).filter(function(item){return !(item == "Ndb_No" || obj[item] == undefined)});
var valuesFiltered = keysFiltered.map(function(item) {return obj[item]});
https://jsfiddle.net/ohea7mgk/
Destructure and get specific attributes from array of object:
const customerList = dealerUserData?.partyDetails.map(
({ partyId, custAccountId }) => ({
partyId,
custAccountId,
customerId: dealerUserData?._id,
userId: dealerUserData?.authUserID,
}),
);

REACT Delete Element in array of objects

I am having trouble deleting an element inside an array of objects. I want to simply delete an object inside the array. When I try to use .IndexOf(). It gives me back -1. is there a way to do this without creating a reference to each object.
This is my Array of objects.
let todos = [{
id: 1,
task: 'Finish This React App'
},{
id: 2,
task: 'Do Another App'
},{
id: 3,
task: 'Pass data from the parent to the child and reverse'
}]
let task = {id:2,task:'Do Another App'}
let todosArray = this.props.todos
todosArray.indexOf(task,0) //= -1
Overall I want to only have objects 1 and 3 inside the todos array.
You can use filter in pure vanillia ES6 :
var array = todos.filter(item => item.id === 1 || item.id === 3);
The Array#indexOf method always returns -1 since the objects reference is not the same.
You can use Array#findIndex, Array#every, Object.keys() and Array#splice methods.
let todos = [{
id: 1,
task: 'Finish This React App'
}, {
id: 2,
task: 'Do Another App'
}, {
id: 3,
task: 'Pass data from the parent to the child and reverse'
}]
let task = {
id: 2,
task: 'Do Another App'
};
let keys = Object.keys(task);
// remove element from the array by index
todos.splice(
// get the index of the element
todos.findIndex(function(obj) {
// check all property values are equal
return keys.every(function(k) {
return task[k] === obj[k];
});
// if you want to check only the `id` property then // you can avoid the above codes and use
// return obj.id === task.id;
}), 1);
console.log(todos)
Note : Above methods only works if there is no nested object and array property value.

Categories