I have an object called featureSet.
Inside of featureSet there are many items, including an array called features, which contains other array, attributes.
I can add a new array inside of featureSet.features.attributes by doing the following within a for loop
featureSet.features[i].attributes.NEWITEM= [NEWITEM_ARRAY];
And when I use console.log(featureSet), I can see that the items are there.
When I use var test = JSON.stringify(featureSet), however, only the original featureSet is returned.
How can I circumvent this so that when I call JSON.stringify, the new items are there as well?
Thank you in advance.
I guess what you do is close to:
let arr = []
console.log(arr) // []
arr.push(1)
console.log(arr) // [1]
arr.abc = 2
console.log(arr.abc) // 2
arr.push(3)
console.log(arr) // [1, 3]
console.log(JSON.stringify(arr)) // '[1, 3]'
console.log(arr.abc) // 2
JSON.stringify loops through array props with the help of Symbol.iterator. Your properties do not have positive integer indexes, that's why they are ignored. There is an example on MDN as well: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify
One more example to consider (continues the one above):
arr[7] = 7
console.log(arr) // [1, 3, undefined, undefined, undefined, undefined, undefined, 7]
console.log(JSON.stringify(arr)) // "[1,3,null,null,null,null,null,7]"
How can I circumvent this so that when I call JSON.stringify, the new items are there as well?
You are trying to append attributes to an array, which will not work.
You can only append attributes to an object.
/* the original feature set */
const featureSet = {
features: [
{feature: 'color',
attributes: {}}, // this is an object, not an array
{feature: 'shape',
attributes: []} // this is an array as per original scenario
]
}
/* adding attributes to an object succeeds */
featureSet.features[0].attributes.NEWITEM = ['this', 'was', 'appended'];
/* adding attributes to an array quietly fails */
featureSet.features[1].attributes.NEWITEM = ['this', 'was', 'not', 'appended'];
const featureSetAsJSONString = JSON.stringify(featureSet)
console.log(featureSetAsJSONString) // notice that feature[0] is as expected, but [1] isn't
Hope this helps.
Cheers,
Try this
featureSet.features[i].attributes.push({NEWITEM: [NEWITEM_ARRAY]})
Related
I face something I don't understand with an array. Indeed, I created an array I have filled with empty subArrays to obtain a 2D Matrix.
But when I manipulate the array it doesn't behave as I expected.
var arr = new Array(5);
arr.fill([]);
arr[2].push("third rank item");
console.log(arr);
//[ [ 'third rank item' ],
// [ 'third rank item' ],
// [ 'third rank item' ],
// [ 'third rank item' ],
// [ 'third rank item' ] ]
Every lights on this matter will be welcomed
This is the same old problem with arrays (and objects in general) being references rather than values.
Specifically, when you do arr.fill([]), you are taking that one single empty array and using that to fill the parent one.
It's like saying:
var arr = new Array(5);
arr[0] = arr[1] = arr[2] = arr[3] = arr[4] = [];
They all refer to the same array! So when you then go on to modify one of them, it looks like they're all modified (but really it's still the same one)
Unfortunately there's no simple way to assign an empty array to each one. You could do something like:
Array.apply(null, Array(5)).map(function() {return [];});
Essentially, create an (initialised) empty array of length 5 and map each (empty) value to a new [].
EDIT: Seems like I'm stuck in old times. As per #torazaburo's comment, you can use Array.from instead of Array.apply(null, Array(5)).map, like so:
Array.from( new Array(5), function() { return []; } );
As you can notice using array.fill you're filling the array with a reference to the same array,
if you want to instantiate each array index to an empty array a normal while loop will do:
var arr = [];
var n = 5
while(n--)
arr[n] = []
arr[2].push("third rank item");
console.log(arr);
Option 2:
if you have lodash package available, you can also use _.map as this is specificaly designed to loop through a sparse array (native map will skip non init values)
var arr =_.map(new Array(5), (x => []))
arr[2].push("third rank item");
console.log(arr)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.2/lodash.min.js"></script>
The eleventh line of the ECMA doc of Array.prototype.fill is clearly giving the reason for the mystery.
Repeat, while k < final
Let Pk be ToString(k).
Let setStatus be Set(O, Pk, value, true).
ReturnIfAbrupt(setStatus).
Increase k by 1.
Here "value" is just a reference received. And they are setting it as a property to array directly. That means all the filled arrays are just reference to a single array object.
It's happens cause of reference. Array is a type of object and object works on their references when you fill your array with [] or new Array() fill run only ones and put the same array in all indexes that's why when you update an sub-array all are updated.
Solution:
let arr = new Array(5).fill(0).map(ele => ele = []);
arr[2].push("something");
OR
let arr = Array.of([], [], [], []);
arr[2].push("something");
Result: as expected only 2 index of arr is updated.
With ES6 I recommend this method to create 2 or multidimensional arrays:
// create an M x N dimension grid and fill it with 0's
const myArray = [...Array(M)].map(r => [...Array(N)].map(r => 0));
Try this ,this is quick solution for you in one line.
var arr = new Array(5);
arr = Array.from(arr, x => []);
arr[2].push("third rank item");
console.log(arr);
You can try this,
var arr = new Array(5);
var i = 0;
while (i < arr.length)
arr.fill([], i++);
arr[2].push("third rank item");
console.log(arr);
A bit different:
let array= JSON.parse(JSON.stringify(new Array(N).fill([])));
or
let array=new Array(N).fill(null).map(()=>[]);
All the answers were correct and reasonable and so I decided to just try it out on my Chrome console. So the answer to the OP is that this is happening because you cannot do a direct fill.
If I understand part of the question is why can't you just do:
const grid = Array(5).fill([])
which is saying okay we have a structure like this:
//[ [ ],
// [ ],
// [ ],
// [ ],
// [ ] ]
And then you are saying go and fill the third one like so:
grid[2].push("third rank item");
But instead of seeing the third one filled with that string, you see all of them. It's simple, you are creating one single array and throwing it in at every location inside of grid. In memory there is only one inner array so you threw that string into that array, it would affect every index.
But I found some behavior slightly different than what I had expected.
If you write the following:
const grid = Array(3).fill([false, false, false]);
You would get the following:
(3) [Array(3), Array(3), Array(3)]
(3) [false, false, false]
(3) [false, false, false]
(3) [false, false, false]
Then if you run: grid[0].push(true), you will get the following:
(3) [Array(4), Array(4), Array(4)]
(4) [false, false, false, true]
(4) [false, false, false, true]
(4) [false, false, false, true]
So modifying one, modifies all, so that's why you cant just do a direct fill and then push as you did, instead you have to do the map() statement like so:
const grid = Array(3)
.fill(null)
.map(() => Array(3).fill(false));
And that will run the inner function three times and each time we generate a brand new and different array.
I have some code here and I was wondering if it is the same thing or different. I am pretty sure these are both suppose to be the same but I wasnt sure if I was doing it right.
let zoneComment = updatedMap[action.comment.zone]
? [...updatedMap[action.comment.zone]] : [];
let zoneComment = updatedMap[action.comment.zone]
? Object.assign([], updatedMap[action.comment.zone]) : [];
If these are the same then which should I use or does it matter? I want to use best practice so if it is your OPINION of which is better then please state so.
In your particular case they are not the same.
The reason is that you have an array, not an object.
Doing ... on an array will spread out all the elements in the array (but not the properties)
Doing Object.assign expects an object so it will treat an array as an object and copy all enumerable own properties into it, not just the elements:
const a = [1, 2, 3];
a.test = 'example';
const one = [...a] // [1, 2, 3];
const two = Object.assign([], a); // { '0': 1, '1': 2, '2': 3, 'test': 'example' }
console.log('\none');
for (let prop in one) {
console.log(prop);
}
console.log('\ntwo');
for (let prop in two) {
console.log(prop);
}
However, if you compare the ... operator applied on an object with Object.assign, they are essentially the same:
// same result
const a = { name: 'test' }
console.log({ ...a })
console.log(Object.assign({}, a))
except ... always creates a new object but Object.assign also allows you to mutate an existing object.
// same result
const a = { name: 'test' }
const b = { ...a, name: 'change' };
console.log(a.name); // test
Object.assign(a, { name: 'change'})
console.log(a.name); // change
Keep in mind that Object.assign is already a part of the language whereas object spread is still only a proposal and would require a preprocessing step (transpilation) with a tool like babel.
To make it short, always use ... spread construction and never Object.assign on arrays.
Object.assign is intended for objects. Although arrays are objects, too, it will cause a certain effect on them which is useful virtually never.
Object.assign(obj1, obj2) gets values from all enumerable keys from obj2 and assigns them to obj1. Arrays are objects, and array indexes are object keys, in fact.
[...[1, 2, 3], ...[4, 5]] results in [1, 2, 3, 4, 5] array.
Object.assign([1, 2, 3], [4, 5]) results in [4, 5, 3] array, because values on 0 and 1 indexes in first array are overwritten with values from second array.
In the case when first array is empty, Object.assign([], arr) and [...arr] results are similar. However, the proper ES5 alternative to [...arr] is [].concat(arr) and not Object.assign([], arr).
Your question really bubbles down to:
Are [...arr] and Object.assign([], arr) providing the same result when arr is an array?
The answer is: usually, yes, but:
if arr is a sparse array that has no value for its last slot, then the length property of the result will not be the same in both cases: the spread syntax will maintain the same value for the length property, but Object.assign will produce an array with a length that corresponds to the index of the last used slot, plus one.
if arr is a sparse array (like what you get with Array(10)) then the spread syntax will create an array with undefined values at those indexes, so it will not be a sparse array. Object.assign on the other hand, will really keep those slots empty (non-existing).
if arr has custom enumerable properties, they will be copied by Object.assign, but not by the spread syntax.
Here is a demo of the first two of those differences:
var arr = ["abc"]
arr[2] = "def"; // leave slot 1 empty
arr.length = 4; // empty slot at index 3
var a = [...arr];
var b = Object.assign([], arr);
console.log(a.length, a instanceof Array); // 4, true
console.log(b.length, b instanceof Array); // 3, true
console.log('1' in arr); // false
console.log('1' in a); // true (undefined)
console.log('1' in b); // false
If however arr is a standard array (with no extra properties) and has all its slots filled, then both ways produce the same result:
Both return an array. [...arr] does this by definition, and Object.assign does this because its first argument is an array, and it is that object that it will return: mutated, but it's proto will not change. Although length is not an enumerable property, and Object.assign will not copy it, the behaviour of the first-argument array is that it will adapt its length attribute as the other properties are assigned to it.
Both take shallow copies.
Conclusion
If your array has custom properties you want to have copied, and it has no empty slots at the end: use Object.assign.
If your array has no custom properties (or you don't care about them) and does not have empty slots: use the spread syntax.
If your array has custom properties you want to have copied, and empty slots you want to maintain: neither method will do both of this. But with Object.assign it is easier to accomplish:
a = Object.assign([], arr, { length: arr.length });
I face something I don't understand with an array. Indeed, I created an array I have filled with empty subArrays to obtain a 2D Matrix.
But when I manipulate the array it doesn't behave as I expected.
var arr = new Array(5);
arr.fill([]);
arr[2].push("third rank item");
console.log(arr);
//[ [ 'third rank item' ],
// [ 'third rank item' ],
// [ 'third rank item' ],
// [ 'third rank item' ],
// [ 'third rank item' ] ]
Every lights on this matter will be welcomed
This is the same old problem with arrays (and objects in general) being references rather than values.
Specifically, when you do arr.fill([]), you are taking that one single empty array and using that to fill the parent one.
It's like saying:
var arr = new Array(5);
arr[0] = arr[1] = arr[2] = arr[3] = arr[4] = [];
They all refer to the same array! So when you then go on to modify one of them, it looks like they're all modified (but really it's still the same one)
Unfortunately there's no simple way to assign an empty array to each one. You could do something like:
Array.apply(null, Array(5)).map(function() {return [];});
Essentially, create an (initialised) empty array of length 5 and map each (empty) value to a new [].
EDIT: Seems like I'm stuck in old times. As per #torazaburo's comment, you can use Array.from instead of Array.apply(null, Array(5)).map, like so:
Array.from( new Array(5), function() { return []; } );
As you can notice using array.fill you're filling the array with a reference to the same array,
if you want to instantiate each array index to an empty array a normal while loop will do:
var arr = [];
var n = 5
while(n--)
arr[n] = []
arr[2].push("third rank item");
console.log(arr);
Option 2:
if you have lodash package available, you can also use _.map as this is specificaly designed to loop through a sparse array (native map will skip non init values)
var arr =_.map(new Array(5), (x => []))
arr[2].push("third rank item");
console.log(arr)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.2/lodash.min.js"></script>
The eleventh line of the ECMA doc of Array.prototype.fill is clearly giving the reason for the mystery.
Repeat, while k < final
Let Pk be ToString(k).
Let setStatus be Set(O, Pk, value, true).
ReturnIfAbrupt(setStatus).
Increase k by 1.
Here "value" is just a reference received. And they are setting it as a property to array directly. That means all the filled arrays are just reference to a single array object.
It's happens cause of reference. Array is a type of object and object works on their references when you fill your array with [] or new Array() fill run only ones and put the same array in all indexes that's why when you update an sub-array all are updated.
Solution:
let arr = new Array(5).fill(0).map(ele => ele = []);
arr[2].push("something");
OR
let arr = Array.of([], [], [], []);
arr[2].push("something");
Result: as expected only 2 index of arr is updated.
With ES6 I recommend this method to create 2 or multidimensional arrays:
// create an M x N dimension grid and fill it with 0's
const myArray = [...Array(M)].map(r => [...Array(N)].map(r => 0));
Try this ,this is quick solution for you in one line.
var arr = new Array(5);
arr = Array.from(arr, x => []);
arr[2].push("third rank item");
console.log(arr);
You can try this,
var arr = new Array(5);
var i = 0;
while (i < arr.length)
arr.fill([], i++);
arr[2].push("third rank item");
console.log(arr);
A bit different:
let array= JSON.parse(JSON.stringify(new Array(N).fill([])));
or
let array=new Array(N).fill(null).map(()=>[]);
All the answers were correct and reasonable and so I decided to just try it out on my Chrome console. So the answer to the OP is that this is happening because you cannot do a direct fill.
If I understand part of the question is why can't you just do:
const grid = Array(5).fill([])
which is saying okay we have a structure like this:
//[ [ ],
// [ ],
// [ ],
// [ ],
// [ ] ]
And then you are saying go and fill the third one like so:
grid[2].push("third rank item");
But instead of seeing the third one filled with that string, you see all of them. It's simple, you are creating one single array and throwing it in at every location inside of grid. In memory there is only one inner array so you threw that string into that array, it would affect every index.
But I found some behavior slightly different than what I had expected.
If you write the following:
const grid = Array(3).fill([false, false, false]);
You would get the following:
(3) [Array(3), Array(3), Array(3)]
(3) [false, false, false]
(3) [false, false, false]
(3) [false, false, false]
Then if you run: grid[0].push(true), you will get the following:
(3) [Array(4), Array(4), Array(4)]
(4) [false, false, false, true]
(4) [false, false, false, true]
(4) [false, false, false, true]
So modifying one, modifies all, so that's why you cant just do a direct fill and then push as you did, instead you have to do the map() statement like so:
const grid = Array(3)
.fill(null)
.map(() => Array(3).fill(false));
And that will run the inner function three times and each time we generate a brand new and different array.
As I know, it is possible to push more data into an array. Fe, I have an array:
G = [12, 34, 5].
Right now, I can access the nth element like this:
G[n]
I'd now like to push new data in it with a label, so I want the array to look like
G = [12, 34, 5, label:567856, other: Infinity]
where I can get 567856 by calling
G["label"] //(or Infinity by calling G["other"]). How can I achieve this?
I've found
G[i].push({
label:567856,
other: Infinity
})
but this way it adds it as a whole new element, and I'm only able to call G[4]["other"], instead of G["other"]. How can I add the element as I've described?
Thank you!
To add onto Andriy's answer, you need to use Javascript Objects rather than arrays. An object can have indices with custom names. For example, given
var newObj = {"hello": "world", "value":1, "inf": Infinity}
you can do
newObj['hello'] // "world"
newObj['value'] // 1
The problem with
G[i].push({
label:567856,
other: Infinity
})
is that you are pushing an object with 2 attributes, not pushing 2 objects, that's why you need to use G[4]["other"]
See running JSFiddle example.
G["other"] = "something";
With this you will keep the original array, and now have the attribute other, but it is not in [12, 34, 5]
Whit this one you can add an object to the array:
G.push({other: 123})
console.log(G);//[12, 34, 5, object]
console.log(G[3].other);//123
The problem with
G[i].push({
label:567856,
other: Infinity
})
is that you are pushing an object with 2 attributes, not pushing 2 objects, that's why you need to use G[4]["other"]
Arrays in JavaScript are a type of object. As such, they can contain properties:
G.label = 567856;
G.other = Infinity;
The advantage of arrays over other objects is that their indexed elements are ordered.
If you'd like the fourth and fifth elements in the array to be 567856 and Infinity and you want to be able to refer to those values with G.label and G.other, you can do so as follows:
var G = [12, 34, 5];
G.push(G.label = 567856); //same as G.label = 567856; G.push(G.label);
G.push(G.other = Infinity);
You can still iterate through the array using a loop:
var G = [12, 34, 5];
G.push(G.label = 567856);
G.push(G.other = Infinity);
G.forEach(function(val) {
console.log(val); // 12 ... 34 ... 5 ... 567856 ... Infinity
});
console.log(G.label); //567856
console.log(G.other); //Infinity
Note that this does create duplicates. If you change G.label or G.other afterwards, those changes will not be reflected in the fourth and fifth elements of the array.
However, you can overcome that by creating setters on G.label and G.other using Object.defineProperty():
var G = [12, 34, 5];
G.push(G.label = 567856);
G.push(G.other = Infinity);
G.forEach(function(val) {
console.log(val); // 12 ... 34 ... 5 ... 567856 ... Infinity
});
console.log(G.label); //567856
console.log(G.other); //Infinity
Object.defineProperty(G, 'label', {
set: function(x) {
this[3] = x;
}
});
Object.defineProperty(G, 'other', {
set: function(x) {
this[4] = x;
}
})
G.label = 99999;
G.other = 11111;
G.forEach(function(val) {
console.log(val); // 12 ... 34 ... 5 ... 99999 ... 11111
});
Arrays isn't designed to suit your case.
See Array element accessing flow from ECMAScript 262, 5.1 15.4
Array objects give special treatment to a certain class of property
names. A property name P (in the form of a String value) is an array
index if and only if ToString(ToUint32(P)) is equal to P and
ToUint32(P) is not equal to 2^32−1.
So you simply cannot access Array element by alphabetical name because that key won't be parsed to integer by ToUint32.
You can add object to array and store it's index after pushing into array ( Array.prototype.push would return you size of your array):
var G = [1,3,4];
var labelIndex = G.push({'label': 123}) - 1;
console.log(G[labelIndex]["label"]);
Actually that's solution would suite case when you have two or more objects inside your array with same property.
Suggestion below not recommended!
However, you can use code below to define your G Array properties, but it's not value of property of item from your array, it's array property:
G.other = Infinity;
G.label = 567856;
// Access newly created properties
console.log(G["other"]);
console.log(G["label"]);
Good Luck !
MDN references JavaScript's Set collection abstraction. I've got an array of objects that I'd like to convert to a set so that I am able to remove (.delete()) various elements by name:
var array = [
{name: "malcom", dogType: "four-legged"},
{name: "peabody", dogType: "three-legged"},
{name: "pablo", dogType: "two-legged"}
];
How do I convert this array to a set? More specifically, is it possible to do this without iterating over the above array? The documentation is relatively lacking (sufficient for instantiated sets; not for conversions - if possible).
I may also be thinking of the conversion to a Map, for removal by key. What I am trying to accomplish is an iterable collection that can be accessed or modified via accessing the elements primarily via a key (as opposed to index).
Conversion from an array to the other being the ultimate goal.
Just pass the array to the Set constructor. The Set constructor accepts an iterable parameter. The Array object implements the iterable protocol, so its a valid parameter.
var arr = [55, 44, 65];
var set = new Set(arr);
console.log(set.size === arr.length);
console.log(set.has(65));
See here
If you start out with:
let array = [
{name: "malcom", dogType: "four-legged"},
{name: "peabody", dogType: "three-legged"},
{name: "pablo", dogType: "two-legged"}
];
And you want a set of, say, names, you would do:
let namesSet = new Set(array.map(item => item.name));
By definition "A Set is a collection of values, where each value may occur only once." So, if your array has repeated values then only one value among the repeated values will be added to your Set.
var arr = [1, 2, 3];
var set = new Set(arr);
console.log(set); // {1,2,3}
var arr = [1, 2, 1];
var set = new Set(arr);
console.log(set); // {1,2}
So, do not convert to set if you have repeated values in your array.
const categoryWithDuplicates =[
'breakfast', 'lunch',
'shakes', 'breakfast',
'lunch', 'shakes',
'breakfast', 'lunch',
'shakes'
]
const categoryWithoutDuplicates =[...new Set(categoryWithDuplicates)];
console.log(categoryWithoutDuplicates)
Output: [ 'breakfast', 'lunch', 'shakes' ]
What levi said about passing it into the constructor is correct, but you could also use an object.
I think what Veverke is trying to say is that you could easily use the delete keyword on an object to achieve the same effect.
I think you're confused by the terminology; properties are components of the object that you can use as named indices (if you want to think of it that way).
Try something like this:
var obj = {
"bob": "dole",
"mr.": "peabody",
"darkwing": "duck"
};
Then, you could just do this:
delete obj["bob"];
The structure of the object would then be this:
{
"mr.": "peabody",
"darkwing": "duck"
}
Which has the same effect.
var yourSetAsArray = Array.from(new Set([1, 2, 2, 3, 3, 4, 4]));
// returns [1, 2, 3, 4]