Related
I have looked at this question in stack overflow about objects guaranteeing order.
Does JavaScript Guarantee Object Property Order?
some says they guarantee, some says they don't, depending on situations. Meantime I've encountered following problem.
I have an array of objects, similar to below:
const arrayObject = [
{id:'a123', bar:'hello'},
{id:'a321', bar: 'foo'} ];
now I wish to turn this arrayObject into object of object, with structure as follows with the same order as the array:
const object = {
'a123': {id:'a123', bar:'hello'},
'a321': {id:'a321', bar: 'foo'},
}
basically using the id of each item in the array as the key of the object. Below is the code I used to try to achieve it:
let newObj = {};
arrayObject.forEach(data=>{
const temp = {
[data.id]:{
id: data.id,
bar: data.bar
},
};
newObj={...newObj, ...temp};
})
I do get the correct structure, however the order is not the same as the order of arrayObject, i.e. it returns:
const object = {
'a321': {id:'a321', bar: 'foo'},
'a123': {id:'a123', bar:'hello'},
}
I've tried with more items in the array, and I get same result. It does not guarantee the order.
Is there something wrong with my code, or is it simply not guaranteeing the order?
What do I have to do to make the object be the same order as the array?
Preserve the order by including ordering information in the (unordered) object. Anytime later, when you need to recover the original order, use the saved ordering information...
const arrayObject = [{
id: 'a123',
bar: 'hello'
},
{
id: 'a321',
bar: 'foo'
}
];
let object = {}
arrayObject.forEach((e, i) => {
object[e.id] = { ...e, orderWith: i } // the index will tell us how to sort later
})
// later on
let sorted = Object.values(object).sort((a, b) => a.orderWith - b.orderWith)
console.log(sorted)
I think what you're looking for is a Map() object. See here -> Map and Set
const arrayObject = [
{id:'a123', bar:'hello'},
{id:'a321', bar: 'foo'},
{id:'a234', bar: 'more'},
{id:'a735', bar: 'words'},
{id:'a167', bar: 'added'},
{id:'a857', bar: 'now'},
];
var newObj = new Map();
for (var i=0; i<arrayObject.length; i++) {
const temp = {
id: arrayObject[i].id,
bar: arrayObject[i].bar
};
newObj.set(arrayObject[i].id, temp);
}
var jsonText = JSON.stringify(Array.from(newObj.entries()));
console.log(jsonText);
// To get at the elements, use .get()
console.log(newObj.get("a321").bar);
A simpler bit of code would be like this (use a for loop instead of forEach:
let newObj = {};
for (const data of arrayObject) {
newObj[data.id] = data;
}
This might get you what you want because it will guarantee that the order the object is built matches the order in the array. Using forEach causes multiple functions to be called in whatever order they run, which might be out of order. But realize that even using the for-loop does not guarantee the order will always match. An Array will guarantee the order, but the Object made this way does not. Even if the above code does give you the desired order, it might not in the future. Use an Array if you need to preserve order.
I want to make an array based on two arrays - "ideaList" and "endorsements" declared globally. As ideaList and endorsements are used in other parts of the program I need them to be immutable, and I thought that .map and .filter would keep this immutability.
function prepareIdeaArray(){
var preFilteredIdeas=ideaList
.filter(hasIdeaPassedControl)
.map(obj => {obj.count = endorsements
.filter(x=>x.ideaNumber===obj.ideaNumber)
.reduce((sum, x)=>sum+x.count,0);
obj.like = endorsements
.filter(x=>x.ideaNumber===obj.ideaNumber && x.who===activeUser)
.reduce((sum, x)=>sum+x.count,0)===0?false:true
obj.position = generatePosition(obj.status)
obj.description = obj.description.replace(/\n/g, '<br>')
return obj;});
preFilteredIdeas.sort(compareOn.bind(null,'count',false)).sort(compareOn.bind(null,'position',true))
return preFilteredIdeas;
}
However, when I console.log ideaList after this function has been executed, I remark that objects of the array all have the "count", "like", "position" properties with values, which proves that the array has been mutated.
I tried by using .map only, but same result.
Would you know how I could prevent ideaList to get mutated? Also I would like to avoid to use const, as I declare ideaList globally first, and then assign to it some data in another function.
You're not mutating the array itself but rather the objects that the array contains references to. .map() creates a copy of the array but the references contained in it points to the exact same objects as the original, which you've mutated by adding properties directly to them.
You need to make copies of these objects too and add the properties to these copies. A neat way to do this is to use object spread in .map() callback:
.map(({ ...obj }) => {
obj.count = endorsements
.filter(x=>x.ideaNumber===obj.ideaNumber)
...
If your environment doesn't support object spread syntax, clone the object with Object.assign():
.map(originalObj => {
const obj = Object.assign({}, originalObj);
obj.count = endorsements
.filter(x=>x.ideaNumber===obj.ideaNumber)
...
In JS, the objects are referenced. When created, in other words, you get the object variable to point to a memory location which intends to be holding a meaningful value.
var o = {foo: 'bar'}
The variable o is now point to a memory which has {foo: bar}.
var p = o;
Now the variable p too is pointing to the same memory location. So, if you change o, it will change p too.
This is what happens inside your function. Even though you use Array methods which wouldn't mutate it's values, the array elements themselves are objects which are being modified inside the functions. It creates a new array - but the elements are pointing to the same old memory locations of the objects.
var a = [{foo: 1}]; //Let's create an array
//Now create another array out of it
var b = a.map(o => {
o.foo = 2;
return o;
})
console.log(a); //{foo: 2}
One way out is to create a new object for your new array during the operation. This can be done with Object.assign or latest spread operator.
a = [{foo: 1}];
b = a.map(o => {
var p = {...o}; //Create a new object
p.foo = 2;
return p;
})
console.log(a); // {foo:1}
To help having immutability in mind you could think of your values as primitives.
1 === 2 // false
'hello' === 'world' // false
you could extend this way of thinking to non-primitives as well
[1, 2, 3] === [1, 2, 3] // false
{ username: 'hitmands' } === { username: 'hitmands' } // false
to better understand it, please have a look at MDN - Equality Comparisons and Sameness
how to force immutability?
By always returning a new instance of the given object!
Let's say we have to set the property status of a todo. In the old way we would just do:
todo.status = 'new status';
but, we could force immutability by simply copying the given object and returning a new one.
const todo = { id: 'foo', status: 'pending' };
const newTodo = Object.assign({}, todo, { status: 'completed' });
todo === newTodo // false;
todo.status // 'pending'
newTodo.status // 'completed'
coming back to your example, instead of doing obj.count = ..., we would just do:
Object.assign({}, obj, { count: ... })
// or
({ ...obj, count: /* something */ })
there are libraries that help you with the immutable pattern:
Immer
ImmutableJS
You use the freeze method supplying the object you want to make immutable.
const person = { name: "Bob", age: 26 }
Object.freeze(person)
You could use the new ES6 built-in immutability mechanisms, or you could just wrap a nice getter around your objects similar to this
var myProvider = {}
function (context)
{
function initializeMyObject()
{
return 5;
}
var myImmutableObject = initializeMyObject();
context.getImmutableObject = function()
{
// make an in-depth copy of your object.
var x = myImmutableObject
return x;
}
}(myProvider);
var x = myProvider.getImmutableObject();
This will keep your object enclosed outside of global scope, but the getter will be accessible in your global scope.
You can read more on this coding pattern here
One easy way to make "copies" of mutable objects is to stringify them into another object and then parse them back into a new array. This works for me.
function returnCopy(arrayThing) {
let str = JSON.stringify(arrayThing);
let arr = JSON.parse(str);
return arr;
}
Actually, you can use spread opreator to make the original array stay unchanged, below y is the immutable array example:
const y = [1,2,3,4,5];
function arrayRotation(arr, r, v) {
for(let i = 0; i < r; i++) {
arr = [...y]; // make array y as immutable array [1,2,3,4,5]
arr.splice(i, 0, v);
arr.pop();
console.log(`Rotation ${i+1}`, arr);
}
}
arrayRotation(y, 3, 5)
If you don't use the spread operator, the y array will get changed when loop is running time by time.
Here is the mutable array result:
const y = [1,2,3,4,5];
function arrayRotation(arr, r, v) {
for(let i = 0; i < r; i++) {
arr = y; // this is mutable, because arr and y has same memory address
arr.splice(i, 0, v);
arr.pop();
console.log(`Rotation ${i+1}`, arr);
}
}
arrayRotation(y, 3, 5)
You assign these properties in your map function, you need to change this. (Just declare an empty object instead of using your current obj)
If I have a JS object, and I'd like to create a new object which copies over all properties except for a blacklist of properties that need to be filtered out, what's the simplest way to do it?
So I would do something like
originalObject.copyAndFilter('a', 'b')
Which would take the original object, copy it, and make sure properties a and b aren't in the new object.
I'm using ES2015/2016 through Babel so if it provides an even simpler way that would work too.
You could use Set and delete the unwanted properties.
var object = { a: 1, b: 2, c: 3, d: 4 },
filteredObject = {},
p = new Set(Object.keys(object));
blacklist = ['a', 'b'];
blacklist.forEach(a => p.delete(a));
[...p].forEach(k => filteredObject[k] = object[k]);
console.log(filteredObject);
Well, there's no simple native way to do it, I would convert the keys to an array, filter it, then create a new object:
const obj = {a: 'foo', b: 'bar', c: 'baz'};
const blacklist = ['a', 'b'];
const keys = Object.keys(obj);
const filteredKeys = keys.filter(key => !blacklist.includes(key));
const filteredObj = filteredKeys.reduce((result, key) => {
result[key] = obj[key];
return result;
}, {});
console.log(filteredObj);
You can just loop over object keys and create a new object. You can even use for...in for it. This will have added advantage of being supported in all browsers.
var obj = {a: 'foo', b: 'bar', c: 'baz'};
var blackListKeys = ['a', 'b','z'];
var obj2 = {}
for(var k in obj){
if(blackListKeys.indexOf(k) === -1)
obj2[k] = obj[k];
}
console.log(obj2)
var obj = {foo: 1, a: 2, b:3}
var newObj = {}
var blacklist = ['a', 'b']
for(let [key, value] of Object.entries(obj)) {
!blacklist.includes(key) && (newObj[key] = value)
}
console.log(newObj)
With fewer variables:
var obj = {foo: 1, a: 2, b: 3}
var newObj = Object.entries(obj)
.filter(([key, val]) => !['a', 'b'].includes(key))
.reduce((newObj, [key, value]) => (newObj[key] = value, newObj), {})
console.log(newObj)
Here there are two problems:
First one is actually copying the object without references.
If you simply loop against object keys and perform assignments of its values from the original object, if that values are also objects, you end up referencing that objects (or even arrays), not copying itself. So you will need to detect that and recursively call your copy_object function to avoid references.
Fortunately that problem is already solved in some libraries which provides an object extend() function like npm (server side) and jQuery (browser).
The trick consist only on extending new freshly created object:
var dst_object = extend({}, src_object);
The second problem is to avoid undesired keys.
Here there are to possible approaches: One is to fully reimplement beforementioned extend() function in a way that they provide a blacklist functionality.
...and the second (and less error prone) is to simply drop undesired keys after copying the object. If they aren't huge data structures, the overhead will be insignificant.
Example:
// npm install --save extend
var extend = require("extend");
function oClone(target, blacklist) {
var dest = extend({}, target);
if (blacklist) {
for (var i=0; i<blacklist.length; i++) {
delete (dest[blacklist[i]]);
};
};
return dest;
};
I think Object.assign is the best ES5 utility to make this with little extra logic:
function copyAndFilter(...args){
var copy = Object.assign( {} , this);
args.forEach(prop => {
delete copy[prop];
}); return copy;
}
Then:
var a = {foo : 'foo' , bar : 'bar'};
a.copyAndFilter = /* function definition above*/
a.copyAndFilter('foo');// { bar : 'bar' }
I would like to get the keys of a JavaScript object as an array, either in jQuery or pure JavaScript.
Is there a less verbose way than this?
var foo = { 'alpha' : 'puffin', 'beta' : 'beagle' };
var keys = [];
for (var key in foo) {
keys.push(key);
}
Use Object.keys:
var foo = {
'alpha': 'puffin',
'beta': 'beagle'
};
var keys = Object.keys(foo);
console.log(keys) // ['alpha', 'beta']
// (or maybe some other order, keys are unordered).
This is an ES5 feature. This means it works in all modern browsers but will not work in legacy browsers.
The ES5-shim has a implementation of Object.keys you can steal
You can use jQuery's $.map.
var foo = { 'alpha' : 'puffin', 'beta' : 'beagle' },
keys = $.map(foo, function(v, i){
return i;
});
Of course, Object.keys() is the best way to get an Object's keys. If it's not available in your environment, it can be trivially shimmed using code such as in your example (except you'd need to take into account your loop will iterate over all properties up the prototype chain, unlike Object.keys()'s behaviour).
However, your example code...
var foo = { 'alpha' : 'puffin', 'beta' : 'beagle' };
var keys = [];
for (var key in foo) {
keys.push(key);
}
jsFiddle.
...could be modified. You can do the assignment right in the variable part.
var foo = { 'alpha' : 'puffin', 'beta' : 'beagle' };
var keys = [], i = 0;
for (keys[i++] in foo) {}
jsFiddle.
Of course, this behaviour is different to what Object.keys() actually does (jsFiddle). You could simply use the shim on the MDN documentation.
In case you're here looking for something to list the keys of an n-depth nested object as a flat array:
const getObjectKeys = (obj, prefix = '') => {
return Object.entries(obj).reduce((collector, [key, val]) => {
const newKeys = [ ...collector, prefix ? `${prefix}.${key}` : key ]
if (Object.prototype.toString.call(val) === '[object Object]') {
const newPrefix = prefix ? `${prefix}.${key}` : key
const otherKeys = getObjectKeys(val, newPrefix)
return [ ...newKeys, ...otherKeys ]
}
return newKeys
}, [])
}
console.log(getObjectKeys({a: 1, b: 2, c: { d: 3, e: { f: 4 }}}))
I don't know about less verbose but I was inspired to coerce the following onto one line by the one-liner request, don't know how Pythonic it is though ;)
var keys = (function(o){var ks=[]; for(var k in o) ks.push(k); return ks})(foo);
Summary
For getting all of the keys of an Object you can use Object.keys(). Object.keys() takes an object as an argument and returns an array of all the keys.
Example:
const object = {
a: 'string1',
b: 42,
c: 34
};
const keys = Object.keys(object)
console.log(keys);
console.log(keys.length) // we can easily access the total amount of properties the object has
In the above example we store an array of keys in the keys const. We then can easily access the amount of properties on the object by checking the length of the keys array.
Getting the values with: Object.values()
The complementary function of Object.keys() is Object.values(). This function takes an object as an argument and returns an array of values. For example:
const object = {
a: 'random',
b: 22,
c: true
};
console.log(Object.values(object));
Year 2022 and JavaScript still does not have a sound way to work with hashes?
This issues a warning but works:
Object.prototype.keys = function() { return Object.keys(this) }
console.log("Keys of an object: ", { a:1, b:2 }.keys() )
// Keys of an object: Array [ "a", "b" ]
// WARN: Line 8:1: Object prototype is read only, properties should not be added no-extend-native
That said, Extending Built-in Objects is Controversial.
If you decide to use Underscore.js you better do
var foo = { 'alpha' : 'puffin', 'beta' : 'beagle' };
var keys = [];
_.each( foo, function( val, key ) {
keys.push(key);
});
console.log(keys);
Is there an elegant way to access the first property of an object...
where you don't know the name of your properties
without using a loop like for .. in or jQuery's $.each
For example, I need to access foo1 object without knowing the name of foo1:
var example = {
foo1: { /* stuff1 */},
foo2: { /* stuff2 */},
foo3: { /* stuff3 */}
};
var obj = { first: 'someVal' };
obj[Object.keys(obj)[0]]; //returns 'someVal'
Object.values(obj)[0]; // returns 'someVal'
Using this you can access also other properties by indexes. Be aware tho! Object.keys or Object.values return order is not guaranteed as per ECMAScript however unofficially it is by all major browsers implementations, please read https://stackoverflow.com/a/23202095 for details on this.
Try the for … in loop and break after the first iteration:
for (var prop in object) {
// object[prop]
break;
}
You can also do Object.values(example)[0].
You can use Object.values() to access values of an object:
var obj = { first: 'someVal' };
Object.values(obj)[0]; // someVal
Use Object.keys to get an array of the properties on an object. Example:
var example = {
foo1: { /* stuff1 */},
foo2: { /* stuff2 */},
foo3: { /* stuff3 */}
};
var keys = Object.keys(example); // => ["foo1", "foo2", "foo3"] (Note: the order here is not reliable)
Documentation and cross-browser shim provided here. An example of its use can be found in another one of my answers here.
Edit: for clarity, I just want to echo what was correctly stated in other answers: the key order in JavaScript objects is undefined.
A one-liner version:
var val = example[function() { for (var k in example) return k }()];
There isn't a "first" property. Object keys are unordered.
If you loop over them with for (var foo in bar) you will get them in some order, but it may change in future (especially if you add or remove other keys).
The top answer could generate the whole array and then capture from the list. Here is an another effective shortcut
var obj = { first: 'someVal' };
Object.entries(obj)[0][1] // someVal
Solution with lodash library:
_.find(example) // => {name: "foo1"}
but there is no guarantee of the object properties internal storage order because it depends on javascript VM implementation.
Here is a cleaner way of getting the first key:
var object = {
foo1: 'value of the first property "foo1"',
foo2: { /* stuff2 */},
foo3: { /* stuff3 */}
};
let [firstKey] = Object.keys(object)
console.log(firstKey)
console.log(object[firstKey])
if someone prefers array destructuring
const [firstKey] = Object.keys(object);
No. An object literal, as defined by MDN is:
a list of zero or more pairs of property names and associated values of an object, enclosed in curly braces ({}).
Therefore an object literal is not an array, and you can only access the properties using their explicit name or a for loop using the in keyword.
To get the first key name in the object you can use:
var obj = { first: 'someVal' };
Object.keys(obj)[0]; //returns 'first'
Returns a string, so you cant access nested objects if there were, like:
var obj = { first: { someVal : { id : 1} }; Here with that solution you can't access id.
The best solution if you want to get the actual object is using lodash like:
obj[_.first(_.keys(obj))].id
To return the value of the first key, (if you don't know exactly the first key name):
var obj = { first: 'someVal' };
obj[Object.keys(obj)[0]]; //returns 'someVal'
if you know the key name just use:
obj.first
or
obj['first']
This has been covered here before.
The concept of first does not apply to object properties, and the order of a for...in loop is not guaranteed by the specs, however in practice it is reliably FIFO except critically for chrome (bug report). Make your decisions accordingly.
I don't recommend you to use Object.keys since its not supported in old IE versions. But if you really need that, you could use the code above to guarantee the back compatibility:
if (!Object.keys) {
Object.keys = (function () {
var hasOwnProperty = Object.prototype.hasOwnProperty,
hasDontEnumBug = !({toString: null}).propertyIsEnumerable('toString'),
dontEnums = [
'toString',
'toLocaleString',
'valueOf',
'hasOwnProperty',
'isPrototypeOf',
'propertyIsEnumerable',
'constructor'
],
dontEnumsLength = dontEnums.length;
return function (obj) {
if (typeof obj !== 'object' && typeof obj !== 'function' || obj === null) throw new TypeError('Object.keys called on non-object');
var result = [];
for (var prop in obj) {
if (hasOwnProperty.call(obj, prop)) result.push(prop);
}
if (hasDontEnumBug) {
for (var i=0; i < dontEnumsLength; i++) {
if (hasOwnProperty.call(obj, dontEnums[i])) result.push(dontEnums[i]);
}
}
return result;
}})()};
Feature Firefox (Gecko)4 (2.0) Chrome 5 Internet Explorer 9 Opera 12 Safari 5
More info: https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/keys
But if you only need the first one, we could arrange a shorter solution like:
var data = {"key1":"123","key2":"456"};
var first = {};
for(key in data){
if(data.hasOwnProperty(key)){
first.key = key;
first.content = data[key];
break;
}
}
console.log(first); // {key:"key",content:"123"}
If you need to access "the first property of an object", it might mean that there is something wrong with your logic. The order of an object's properties should not matter.
A more efficient way to do this, without calling Object.keys() or Object.values() which returns an array:
Object.prototype.firstKey = function () {
for (const k in this) {
if (Object.prototype.hasOwnProperty.call(this, k)) return k;
}
return null;
};
Then you can use it like:
const obj = {a: 1, b: 2, c: 3}
console.log(obj.firstKey()) //=> 'a'
This doesn't necessarily return the first key, see Elements order in a "for (… in …)" loop
we can also do with this approch.
var example = {
foo1: { /* stuff1 */},
foo2: { /* stuff2 */},
foo3: { /* stuff3 */}
};
Object.entries(example)[0][1];
This is an old question but most of the solutions assume that we know the attribute's name which it is not the case for example if you are trying to visualize data from files that the user can upload or similar cases.
This is a simple function that I use and works in both cases that you know the variable and if not it will return the first attribute of the object (sorted alphabetically)
The label function receives an object d and extract the key if exits, otherwise returns the first attribute of the object.
const data = [
{ label: "A", value: 10 },
{ label: "B", value: 15 },
{ label: "C", value: 20 },
{ label: "D", value: 25 },
{ label: "E", value: 30 }
]
const keys = ['label', 0, '', null, undefined]
const label = (d, k) => k ? d[k] : Object.values(d)[0]
data.forEach(d => {
console.log(`first: ${label(d)}, label: ${label(d, keys[0])}`)
})
keys.forEach(k => {
console.log(`label of ${k}: ${label(data[0], k)}`)
})
For values like 0, '', null, and undefined will return the first element of the array.
this is my solution
const dataToSend = {email:'king#gmail.com',password:'12345'};
const formData = new FormData();
for (let i = 0; i < Object.keys(dataToSend).length; i++) {
formData.append(Object.keys(dataToSend)[i],
Object.values(dataToSend)[i]);
}
console.log(formData);
Any reason not to do this?
> example.map(x => x.name);
(3) ["foo1", "foo2", "foo3"]
Basic syntax to iterate through key-value gracefully
const object1 = {
a: 'somestring',
b: 42
};
for (const [key, value] of Object.entries(object1)) {
console.log(`${key}: ${value}`);
}
// expected output:
// "a: somestring"
// "b: 42"
As others have pointed out, if the order of properties is important, storing them in an object is not a good idea. Instead, you should use an array via square brackets. E.g.,
var example = [ {/* stuff1 */}, { /* stuff2 */}, { /* stuff3 */}];
var first = example[0];
Note that you lose the 'foo' identifiers. But you could add a name property to the contained objects:
var example = [
{name: 'foo1', /* stuff1 */},
{name: 'foo2', /* stuff2 */},
{name: 'foo3', /* stuff3 */}
];
var whatWasFirst = example[0].name;
For those seeking an answer to a similar question, namely: "How do I find one or more properties that match a certain pattern?", I'd recommend using Object.keys(). To extend benekastah's answer:
for (const propName of Object.keys(example)) {
if (propName.startsWith('foo')) {
console.log(`Found propName ${propName} with value ${example[propName]}`);
break;
}
}