I have the following context:
https://jsfiddle.net/eqntaqbt/2/
obj.forEach(function(user, index){
var userName = user.name;
console.log(index, userName);
if(index === 5 || index === 2){
obj.splice(index, 1);
}
});
I am using a forEach loop and splice to remove the item in position 5 and 2 on the obj array. But for some reason its not working properly.
What am I doing wrong?
Your code is splicing while looping. Spliced elements are accessed even they are no more existing. That leads to undefined elements.
You may consider Array#filter
var obj = [{ "index": 0, "name": "Odonnell Noble", "gender": "male", "company": "DIGIQUE", "eail": "odonnellnoble#digique.com" }, { "index": 1, "name": "Marie Oneal", "gender": "female", "company": "CANOPOLY", "email": "marieoneal#canopoly.com" }, { "index": 2, "name": "Adrienne Marsh", "gender": "female", "company": "XOGGLE", "email": "adriennemarsh#xoggle.com" }, { "index": 3, "name": "Goff Mullins", "gender": "male", "company": "ENDIPIN", "email": "goffmullins#endipin.com" }, { "index": 4, "name": "Lucile Finley", "gender": "female", "company": "AQUASSEUR", "email": "lucilefinley#aquasseur.com" }, { "index": 5, "name": "Pitts Mcpherson", "gender": "male", "company": "QUARX", "email": "pittsmcpherson#quarx.com" }];
obj = obj.filter(function (user, index) {
return (user.index !== 5 && user.index !== 2);
});
document.write('<pre>' + JSON.stringify(obj, 0, 4) + '</pre>');
From Array#forEach
The range of elements processed by forEach() is set before the first
invocation of callback. Elements that are appended to the array after
the call to forEach() begins will not be visited by callback. If the
values of existing elements of the array are changed, the value passed
to callback will be the value at the time forEach() visits them;
elements that are deleted before being visited are not visited.
obj.forEach(function(user, index){
var userName = user.name;
//console.log(index, userName);
if(user.index === 5 || user.index === 2){
this.splice(index, 1);
}
}.bind(obj));
Here is the working fiddle
forEach is rather meant for so-called side-effects.
The problem with your code is, that you are changing the array while you are iterating over it. So if you remove one item, all the other indices of the array are reassigned immediately. That's why after removing one item, further removals don't do the desired thing (at the desired position).
So forEach is good for effecting things that are outside the actual array, that is iterated.
This would be a perfect usecase for a function called filter, since that's actually, what you are doing with your list: you want to filter out some items.
array = array.filter(function(item, index) {
return (index !== 5 && index !== 2)
}
Filter excepts a function as an argument, which itself will be called for each item in the array. If the function returns true for an item, it's kept - otherwise removed. That's why the logical expression must be slightly changed here: It reads like: Keep the items, which are not of index 5 and not of index 2. These sort of true-or-false returning functionas are called predicates.
What if you want to filter out some more indices? The expression using locical operators becomes quickly quite long.
You can instead use the array method indexOf on a list of indices, passing each time the current index of your array to it. Either this will return the a position or -1 if it's not in it. In the later case you want to keep the item in the array.
array = array.filter(function(item, current_index) {
return ([2, 5].indexOf(current_index) === -1)
}
Also, you could wrap that in a function:
function removeIndices(array, indices) {
return array.filter(function(item, current_index) {
return (indices.indexOf(current_index) === -1)
})
}
Finally:
array = removeIndices(array, [2, 5]);
Related
What I have to do is filter the array and write selected elements in the new one. Previously I saw two possible ways, but thanks to comments bellow I realised that way with deleting elements from array is not good. So what I need to do is to copy some elements (i.e. everything with category "science") to new one. My first question is: which approach is better? more efficient? Thank you for your help!
Now second question. How can I copy elements from my array to the new one? It was not a problem to copy only a book name form element with id 0. But when I was trying to copy id, name, category, price and image is exactly the same form to the new array the attempt failed...
My array is:
var products =
[
{
"id": "0",
"name": "Book Name",
"category": "science",
"price": "$49,99",
"image": "img/product-1.png"
},
{
"id": "1",
"name": "Book Name 2",
"category": "computers",
"price": "$319",
"image": "img/product-2.png"
},
{
"id": "2",
"name": "Book Name 3",
"category": ["science", "programming", "computers"],
"price": "$49,99",
"image": "img/product-1.png"
}
]
below I tried to remove mismatched elements, but this approach turned out to be less effective and more problematic.
let checkedOperations = 'programming';
let selected_products = [... products];
$(document).ready(function () {
for (var i in products) {
if (isTrue(checkedOperations, products[i].category)) {
selected_products.splice(i,1);
}
console.log('selected products after slice -1 = ' + selected_products[1].name);
console.log('selected products after slice = ' + selected_products[2].name);
console.log('selected products after slice +1 = ' + selected_products[3].name);
}
});
Using forEach should do the trick without handling indexes and thus making it less error-prone.
let resArray = []; // Filtered result array
products.forEach((el) => {
if (el.id === "0") { // Whatever condition you want
resArray.push(el);
}
})
I'd like to know if one can use .map() to dynamically change the added value to JS objects.
For example, a static use of .map() allows to add a similar ID to all objects of the array.
friends = [
{
"age": 10,
"name": "Castillo"
},
{
"age": 11,
"name": "Daugherty"
},
{
"age": 12,
"name": "Travis"
}
]
// Static mapping --> adds 1 to all objects
friends_static=friends;
friends.map(elem => elem["id"] = 1);
console.log(friends_static)
This returns [{age=10, name="Castillo", id=1}, {age=11, name="Daugherty", id=1}, {age=12, name="Travis", id=1}]
Is it possible to add a unique ID which increments by 1 for each object in a similar way?
Cf. the illustrative JSfiddle and example code below. I know the 1++ is not legal, but just shows the idea I'm trying to realize.
//Dynamic mapping? --> should add 1,2,3...to objects incrementally
/*
friends_dynamic=friends;
friends.map(elem => elem["id"] = 1++);
console.log(friends_dynamic)
*/
This should return [{age=10, name="Castillo", id=1}, {age=11, name="Daugherty", id=2}, {age=12, name="Travis", id=3}]
You could just use the index provided to the Array#map callback:
friends.map((friend, index) => Object.assign({}, friend, { id: index + 1 }))
It's not a good idea to mutate objects in Array#map. The whole purpose of the method is to return new objects that are mapped from the original objects. Thus use Object.assign to avoid mutation.
Of course, if you wanted mutation, thus just use forEach without mapping to new values. It would be more "semantically correct" in that case.
Is this what you mean?
const friends = [
{
"age": 10,
"name": "Castillo"
},
{
"age": 11,
"name": "Daugherty"
},
{
"age": 12,
"name": "Travis"
}
]
friends.forEach((friend, index) => friend.id = index + 1);
console.log(friends)
if you only need an incremental value from 0 on, you can simply use a counter and increment it, like this:
let id = 1;
friends.map(elem => {elem.id = id++;});
Use a local variable and increment it. As per method definition
"The map() method calls the provided function once for each element in an array, in order". In Order would make sure that ids do not collide.
friends = [
{
"age": 10,
"name": "Castillo"
},
{
"age": 11,
"name": "Daugherty"
},
{
"age": 12,
"name": "Travis"
}
]
// Static mapping --> adds 1 to all objects
friends_static=friends;
var i = 1;
friends_static.map(elem => elem["id"] = i++);
console.log(friends_static)
//Dynamic mapping? --> should add 1,2,3...to objects incrementally
/*
friends_dynamic=friends;
friends_dynamic.map(elem => elem["id"] = 1++);
console.log(friends_dynamic)
*/
I am trying to get the same results as pythons json.dumps() with sort by keys enabled. This is preformed as a pre-request script for Postman to generate the request hash. The output needs to be sorted valid json which is used as input for hashing. I am new to javascript and see many old answers claiming that objects in javascript cannot be sorted. However there must be a solution to generate the hash given the criteria.
The object structure cannot be changed.
It only needs to support Chrome.
I can use libraries.
requestParams can contain nested objects and arrays which need to be sorted at any depth.
This is my current code. In the Chrome console the object preview for sortedResult is unsorted, however when I expand the object and sub-objects the Chrome console shows sortedResult as sorted, exactly the way it should be. This gives me the impression the sortObject is working. However requestOrdered returns the valid json object but it is not sorted. My initial thoughts are that maybe JSON.stringify() is unsorting it.
const requestRebuilt = {"username": user, "password": password, "sTime": time, "function": function,
"functionParams": requestParams, "salt": salt};
function sortObject(object){
var keys = _.keys(object);
var sortedKeys = _.sortBy(keys, function(key){
//console.log(key);
return key;
});
var sortedObj = {};
var sortedObjJson = "";
for(var index in keys){
var key = keys[index];
//console.log(key + ' ' + typeof object[key]);
if(typeof object[key] == 'object' && !(object[key] instanceof Array)){
sortedObj[key] = sortObject(object[key]);
} else if(object[key] instanceof Array) {
//sortedObj[key] = object[key].sort();
var arrayLength = object[key].length;
for (var i = 0; i < arrayLength; i++) {
sortedObj[key] = sortObject(object[key][i]);
//console.log(object[key][i]);
}
} else {
sortedObj[key] = object[key];
}
}
return sortedObj;
}
const sortedResult = sortObject(requestRebuilt);
console.log(sortedResult);
const requestOrdered = JSON.stringify(sortedResult);
console.log(requestOrdered);
var hash = CryptoJS.SHA256(requestOrdered).toString();
postman.setGlobalVariable("hash", hash);
Example input:
{
"username": "jdoe#mail.com",
"sTime": "2016-03-04T13:53:37Z",
"function": "begin",
"functionParams": {
"tip": "ABC123FFG",
"pad": 4 ,
"passenger": [{
"firstName": "John",
"phone": 1234567890,
"email": "jdoe#mail.com",
"dateOfBirth": "1915-10-02T00:00:00Z",
"bans": {
"weight": 9,
"count": 2
}
}
]},
"salt": "00d878f5e203",
"pep": "sdeODQ0T"
}
In python this is done by the following:
ordered = json.dumps(
{"username": user, "password": password, "time": time, "function": function, "functionParams": functionParams, "salt": salt}
sort_keys=True, separators=(',', ':'))
Result of ordered:
{"function":"begin","functionParams":{"passenger":[{"bans":{"count":2,"weight":9},"dateOfBirth":"1915-10-02T00:00:00Z","email":"jdoe#mail.com","firstName":"John","phone":1234567890}],"pad":4,"tip":"ABC123FFG"},"pep":"sdeODQ0T","salt":"00d878f5e203","sTime":"2016-03-04T13:53:37Z","username":"jdoe#mail.com"}
Pretty printed for easier reading but actual result should not have spaces or new lines:
{
"function": "begin",
"functionParams": {
"passenger": [
{
"bans": {
"count": 2,
"weight": 9
},
"dateOfBirth": "1915-10-02T00:00:00Z",
"email": "jdoe#mail.com",
"firstName": "John",
"phone": 1234567890
}
],
"pad": 4,
"tip": "ABC123FFG"
},
"pep": "sdeODQ0T",
"salt": "00d878f5e203",
"sTime": "2016-03-04T13:53:37Z",
"username": "jdoe#mail.com"
}
It's a common misconception that "object keys are not ordered" in javascript. MDN states that
Although ECMAScript makes iteration order of objects implementation-dependent, it may appear that all major browsers support an iteration order based on the earliest added property coming first (at least for properties not on the prototype).
and ES2015 makes this behaviour standard:
For each own property key P of O that is a String but is not an integer index, in property creation order...
That is, you can rely on the fact that object properties are always iterated in the insertion order (unless you're using delete, see here for details).
So, to sort keys in some object just create a new object and add keys to it in the sorted order:
function sortKeys(x) {
if (typeof x !== 'object' || !x)
return x;
if (Array.isArray(x))
return x.map(sortKeys);
return Object.keys(x).sort().reduce((o, k) => ({...o, [k]: sortKeys(x[k])}), {});
}
////
obj = {
"username": "jdoe#mail.com",
"sTime": "2016-03-04T13:53:37Z",
"function": "begin",
"functionParams": {
"tip": "ABC123FFG",
"pad": 4,
"passenger": [{
"firstName": "John",
"phone": 1234567890,
"email": "jdoe#mail.com",
"dateOfBirth": "1915-10-02T00:00:00Z",
"bans": {
"weight": 9,
"count": 2
}
}
]
},
"salt": "00d878f5e203",
"pep": "sdeODQ0T"
}
sorted = sortKeys(obj);
console.log(sorted);
Basically I am transforming a JSON result into html and using $.each it iterate through multiple keys. For example, I am pulling back facebook posts and iterating through the likes in that post.
The problem lies in the fact that when there are multiple "likes" everything works great! although when there is only 1 "like" the "source" key is removed from the result set and my javascript breaks because I expect it to be there. Any idea why the $.each is skipping a level for single nodes? The following is my code:
* JQUERY **
$.each(post.likes.item, function(i, like){
$(currentpost).find('div.cc_likes').append(like + ',');
console.log(like)
});
* JSON RESULT **
* Single Like
likes": {
"item": {
"source": {
"cta": "Mary Smith",
"url": "http:\/\/www.facebook.com\/",
"photo": {
"image": "https:\/\/graph.facebook.com\/"
}
}
},
Result in console:
Object
cta: "MaryAnn Smith"
photo: Object
url: "http://www.facebook.com/"
* Multiple Likes
"likes": {
"item": [
{
"source": {
"cta": "Bobby Carnes Sr.",
"url": "http:\/\/www.facebook.com",
"photo": {
"image": "https:\/\/graph.facebook.com\"
}
}
},
{
"source": {
"cta": "Jenna Purdy",
"url": "http:\/\/www.facebook.com\",
"photo": {
"image": "https:\/\/graph.facebook.com\"
}
}
},
{
"source": {
"cta": "Kevin Say",
"url": "http:\/\/www.facebook.com\",
"photo": {
"image": "https:\/\/graph.facebook.com\"
}
}
}
],
"count": "10",
"count_display": "10"
},
Result in console:
Object
source: Object
cta: "Kevin Smith"
photo: Object
url: "http://www.facebook.com/"
Since $.each() needs an array or array like object as argument, before using the object post.likes.item check if it is an array of not.
Following code will always pass an array to jQuery -
$.each([].concat(post.likes.item), function(i, like){
$(currentpost).find('div.cc_likes').append(like + ',');
console.log(like)
});
Explanation
[] is an empty array in JavaScript. Every array in JavaScript has a concat method.
[].concat(obj) concats obj to the empty array and returns an array.
if obj is not an array, result is [obj] which is an array with one item.
if obj is an array, then result is a deep copy of obj which is already an array.
More about concat method
if ( isArray ) {
for ( ; i < length; i++ ) {
value = callback.call( obj[ i ], i, obj[ i ] );
if ( value === false ) {
break;
}
}
} else {
for ( i in obj ) {
value = callback.call( obj[ i ], i, obj[ i ] );
if ( value === false ) {
break;
}
}
}
That is the jquery code being run on your JSON return. What's happening is, when you are looking at multiple results, it is looping through the array, return each base level object. However, when you are running it on a single return, it is looping through the object properties(in this case, "source"), and returning the value of that property.
You have two choices here. You can either make sure single items are still put in an array, or you can do a check for single items on the client side. The way Moazzam Khan suggests is the best way to do it in most cases.
Not quite grasping what's going on here. Given the array (arr):
[
{
"first_name": "Dan",
"last_name": "Woodson",
"id": 1
},
{
"first_name": "Jen",
"last_name": "Woodson",
"id": 2
},
{
"first_name": "Yoshi",
"last_name": "Woodson",
"id": 3
}
]
And the object (obj):
{
"first_name": "Yoshi",
"last_name": "Woodson",
"id": 3
}
Why would arr.indexOf(obj) return -1 (especially since I retrieved the object from the array using it's 'id' parameter earlier in the function)?
Array.indexOf() will only work on objects if the supplied object is exactly the same object you put in.
An exact copy is insufficient, it has to be the exact same object, i.e. there must be some object in the array such that:
arr[i] === obj
You need to show how you retrieved the object.
I would like to see the retrieve function, but most likely you are not using the same reference. Because the following is true:
var a = {id: 3};
var b = [a];
b.indexOf(a); // 0
a.id = "not three";
b.indexOf(a); // still 0
However, the following will break:
var a = {id: 3};
var b = [{id: 3}];
b.indexOf(a); // -1 not the same object