I have a read only array that i copied to become a mutable array let mutableForecast = [...forecast] I am taking that new array and iterating through it with forEach so i can mutate the array. im trying to use some flow control with a switch statement, but I am getting TypeError: Cannot assign to read only property 'location' of object '#<Object>'
let mutableForecast = [...forecast]
mutableForecast.forEach((obj, i) => {
switch (obj.location) {
case obj.location === "BRITISH_COLUMBIA":
obj.location = "BC"
break;
default:
obj.location = "oother"
}
})
Whats the issue here? I've look at this, this, this and some others but cannot find an answer.
This is what the forecast array looks like before i copied it
It's hard to be sure without knowing where forecast comes from, but I suspect the problem is that the elements of the array are not plain objects, but instances of a custom type that are defined as immutable. Your third link has the likely solution. The key is that you can't convert an array of immutables into an array of mutables simply by using rest & spread in this way. You need to modify the mutability of each item in the array individually.
You probably need something like this:
let mutableForecast = [...forecast]
mutableForecast.forEach((obj, i) => {
// make this element's location property mutable
Object.defineProperty(obj, 'location', { writable: true })
// calculate and set new value
switch (obj.location) {
case 'BRITISH_COLUMBIA':
obj.location = 'BC'
break;
default:
obj.location = 'other'
}
})
This might also work, and I think it's cleaner. You'd have to try it to be sure:
let mutableForecast = Array.from(forecast)
.map(forecastItem => ({
...forecastItem,
location: getShortLocation(forecastItem.location)
}))
function getShortLocation( sourceLocation ) {
switch (sourceLocation) {
case 'BRITISH_COLUMBIA': return 'BC'
default: return 'other'
}
}
The core problem we're working around is that whatever package gives you forecast, it clearly trafficks in some custom datatype, some of whose properties are defined as immutable. That fact doesn't show up when you log the objects, and it isn't changed when you convert an array-like container into an array.
That's because [...forecast] doesn't edit the items, it just copies them as-is from one data structure into another. Actually, be to precise, it copies references to those objects into a new array. If the original objects are weird things with locked properties, then your new array will consist of weird things with locked properties. If we want to change the value of that property on each element, we need to redefine the property before doing so.
Consider a case like this:
let myDog = {
species: 'dog',
name: 'Fido'
}
//> myDog { species: 'dog', name: 'Fido' }
We can create another object with the same properties like so:
let congruentAnimal = {
...myDog
}
//> congruentAnimal { species: 'dog', name: 'Fido' }
If the same property names occurs twice, the engine will only honor the last one:
let myDog = {
species: 'cat',
name: 'Fido',
species: 'dog' // this will cause cat to be ignored
}
//> myDog { name: 'Fido', species: 'dog' }
So, we can override individual object properties while copying by re-declaring those properties last:
let anotherCongruentAnimal = {
...myDog,
species: 'NEW DOG'
}
//> anotherCongruentAnimal { name: 'Fido', species: 'NEW DOG' }
That's what is going on in that second snippet. Here's an expanded version:
// create a real array whose elements are *references* to
// the objects in the array-like forecast
let arrayOfImmutableForecasts = Array.from(forecast)
// create another real array of new objects
// whose property names, values, and metadata are
// the same as the source objects
let arrayOfMutableForecasts = arrayOfImmutableForecasts.map(originalObject => {
let newObject = {
// I think this will also preserve special rules like immutability
...originalObject,
// before we finalize the object, we declare a new simple property
// the engine will _drop_ the implied prop declaration from above
// and define the prop based on this simple declaration instead
location: 'new value'
}
return newObject
})
It seems like you are not allowed to mutate the location property of the objects in the array...
You can try creating a clone of the object and mutate that:
let mutableForecast = [...forecast]
mutableForecast = mutableForecast.map(obj => {
const location = obj.location
const objClone = {}
for (const key in obj) {
if (key !== 'location') objClone[key] = obj[key]
}
switch (location) {
case "BRITISH_COLUMBIA":
objClone.location = "BC"
break;
default:
objClone.location = "other"
}
return objClone
})
If that fails, you can try creating a new property insteade, and later read that property:
let mutableForecast = [...forecast]
mutableForecast.forEach((obj, i) => {
switch (obj.location) {
case "BRITISH_COLUMBIA":
obj.newLocation = "BC"
break;
default:
obj.newLocation = "other"
}
})
I'm sure this question must have been answered before, so a link (or what specifically to ask Google) would suffice, but what is the best way to do a check in a scenario like this (and does the new ? operator help in scenarios like this):
/**
* An API returns a job object like:
* { id: 123, name: 'The Job', details: [ { detail_name: "Foo", some: "thing" }, { detail_name: "Bar", some: "thing else" } ] }
*/
const fooDetail = job.details.find(attr => {
return attr.detail_name === 'Foo' });
if (fooDetail && fooDetail.detail_name === "Foo") {
// todo process `some: "thing"`
}
It seems long winded to have to find an object in an array based on a property, but then having to check again that the object exists before checking the property (or get a can't get detail_name of undefined error). Is there a better/shorter way?
You can use Array.some
This will return true if one is found..
The same callback as find can be used
const hasDetail = job.details.some(attr => {
return attr.detail_name === 'Foo' });
if (hasDetail) {
// todo process `some: "thing"`
}
If you're simply want to get the value and then later on check if the property exists, you can use find and ?. operator
const addDetail = job.details.find(attr => {
return attr.detail_name === 'Foo' });
if (addDetail?.detail_name === 'Foo') {
// todo process `some: "thing"`
}
You can do also
job.details.forEach(x=>{
if(x.detail_name == "Foo") {
console.log(x.some);
}
});
I have an object with objects, which are basically settings per guild. In these objects are various configuration options that the admin of their guild can change.
{
"1": {
"foo": "Hello World",
"bar": "Ello World",
"roo": {
"doo": "oof"
}
},
"2": {
"foo": "foo bar foo bar",
"bar": "World! Hi!",
"roo": {
"doo": "boo!"
}
}
}
And I have a default object for those settings.
const Default = {
foo: "Hello, World!",
bar: "Foo example",
roo: {
doo: "boo"
}
};
When I add a new key to the default object, I'd like all the objects in the settings to adapt to these new changes and add only the new key with the default value. Although, I don't know which key is new, because only at startup I want the script to check for new values and add those to the existing settings.
Also, settings can have nested objects but no arrays - arrays are only used as value. I've looked at this answer but I cannot seem to figure out how to add nested object support
For instance, I could add a "foobarexample" key to the default object, and as default value "ello" and it would add that to all the settings. I should also be able to add this to the 'roo' object and it would still update, even though 'roo' isn't new
If you have any suggestions, they're much appreciated! Thanks
This is a perfect use case for JavaScript's prototypal inheritance:
const defaultObj = {
foo: "Hello, World!",
bar: "Foo example",
roo: {
doo: "boo"
}
};
const obj = Object.assign(Object.create(defaultObj), { foo: "New foo" });
const anotherObj = Object.create(defaultObj);
console.log('obj.foo', obj.foo);
console.log('anotherObj.foo', anotherObj.foo);
console.log('obj.bar', obj.bar);
defaultObj.bar = "New bar";
console.log('obj.bar', obj.bar);
console.log('anotherObj.bar', anotherObj.bar);
defaultObj.foobarexample = "ello";
console.log('obj.foobarexample ', obj.foobarexample );
console.log('anotherObj.foobarexample ', anotherObj.foobarexample );
In this code, all of the objects created from defaultObj with Object.create have a reference on defaultObj. When trying to access a property of the resulting objects, the property will be looked up in the object itself, and then in defaultObj if not found. defaultObj can safely be mutated after the objects have been created (it cannot be reassigned though, because the objects created would keep a reference on the previous one).
https://repl.it/#HenriDe/httpsreplitHenriDeIntersectinobjectswithdefault
this one should work for what you are looking for.
const defaultValues = {
"1" : {
foo: 'Hello, World!',
bar: 'Foo example',
roo: {
doo: 'boo',
newest: {
goal: "dodo"
}
},
hello: "world"
}
};
function eachRecursive(obj, defaultValues, level = 0){
// check each key amongst the defaults
for (var k in defaultValues){
// if our original object does not have key attach it + default values associated
if (!obj.hasOwnProperty(k)) {
obj[k] = checkWith[k]
}
// keep calling as long as value is object
if (typeof obj[k] == "object" && obj[k] !== null)
eachRecursive(obj[k], checkWith[k], level + 1);
}
}
}
// for each of the specific entries check
for (let [key, value] of Object.entries(data)) {
eachRecursive(data[key], defaultValues["1"])
}
console.log(data["1"])
Is there a way to set the default attribute of a Javascript object such that:
let emptyObj = {};
// do some magic
emptyObj.nonExistingAttribute // => defaultValue
Since I asked the question several years ago things have progressed nicely.
Proxies are part of ES6. The following example works in Chrome, Firefox, Safari and Edge:
let handler = {
get: function(target, name) {
return target.hasOwnProperty(name) ? target[name] : 42;
}
};
let emptyObj = {};
let p = new Proxy(emptyObj, handler);
p.answerToTheUltimateQuestionOfLife; //=> 42
Read more in Mozilla's documentation on Proxies.
Use destructuring (new in ES6)
There is great documentation by Mozila as well as a fantastic blog post that explains the syntax better than I can.
To Answer Your Question
var emptyObj = {};
const { nonExistingAttribute = defaultValue } = emptyObj;
console.log(nonExistingAttribute); // defaultValue
Going Further
Can I rename this variable? Sure!
const { nonExistingAttribute: coolerName = 15} = emptyObj;
console.log(coolerName); // 15
What about nested data? Bring it on!
var nestedData = {
name: 'Awesome Programmer',
languages: [
{
name: 'javascript',
proficiency: 4,
}
],
country: 'Canada',
};
var {name: realName, languages: [{name: languageName}]} = nestedData ;
console.log(realName); // Awesome Programmer
console.log(languageName); // javascript
There isn't a way to set this in Javascript - returning undefined for non-existent properties is a part of the core Javascript spec. See the discussion for this similar question. As I suggested there, one approach (though I can't really recommend it) would be to define a global getProperty function:
function getProperty(o, prop) {
if (o[prop] !== undefined) return o[prop];
else return "my default";
}
var o = {
foo: 1
};
getProperty(o, 'foo'); // 1
getProperty(o, 'bar'); // "my default"
But this would lead to a bunch of non-standard code that would be difficult for others to read, and it might have unintended consequences in areas where you'd expect or want an undefined value. Better to just check as you go:
var someVar = o.someVar || "my default";
my code is:
function(s){
s = {
top: s.top || 100, // default value or s.top
left: s.left || 300, // default value or s.left
}
alert(s.top)
}
The way I achieve this is with the object.assign function
const defaultProperties = { 'foo': 'bar', 'bar': 'foo' };
const overwriteProperties = { 'foo': 'foo' };
const newObj = Object.assign({}, defaultProperties, overwriteProperties);
console.log(defaultProperties); // {"foo": "bar", "bar": "foo"}
console.log(overwriteProperties); // { "foo": "foo" };
console.log(newObj); // { "foo": "foo", "bar": "foo" }
This seems to me the most simple and readable way of doing so:
let options = {name:"James"}
const default_options = {name:"John", surname:"Doe"}
options = Object.assign({}, default_options, options)
Object.assign() reference
This sure sounds like the typical use of protoype-based objects:
// define a new type of object
var foo = function() {};
// define a default attribute and value that all objects of this type will have
foo.prototype.attribute1 = "defaultValue1";
// create a new object of my type
var emptyObj = new foo();
console.log(emptyObj.attribute1); // outputs defaultValue1
I think the simplest approach is using Object.assign.
If you have this Class:
class MyHelper {
constructor(options) {
this.options = Object.assign({
name: "John",
surname: "Doe",
birthDate: "1980-08-08"
}, options);
}
}
You can use it like this:
let helper = new MyHelper({ name: "Mark" });
console.log(helper.options.surname); // this will output "Doe"
Documentation (with polyfill):
https://developer.mozilla.org/it/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
Or you can try this
dict = {
'somekey': 'somevalue'
};
val = dict['anotherkey'] || 'anotherval';
Simplest of all Solutions:
dict = {'first': 1,
'second': 2,
'third': 3}
Now,
dict['last'] || 'Excluded'
will return 'Excluded', which is the default value.
If you only have an object that is a single level deep (nested object properties will not merge as expected since it directly destructures from the first level), you can use the following destructuring syntax:
const options = {
somevar: 1234,
admin: true
};
const defaults = {
test: false,
admin: false,
};
var mergedOptions = {...defaults, ...options};
Of which the output would be:
console.log(options);
// { somevar: 1234, admin: true }
console.log(mergedOptions);
// { test: false, admin: true, somevar: 1234 }
Or even formatted as a single statement (this is slightly unreadable though):
const options = {...{
// Defaults
test: false,
admin: false,
}, ...{
// Overrides
somevar: 1234,
admin: true
}};
I saw an article yesterday that mentions an Object.__noSuchMethod__ property: JavascriptTips I've not had a chance to play around with it, so I don't know about browser support, but maybe you could use that in some way?
I'm surprised nobody has mentioned ternary operator yet.
var emptyObj = {a:'123', b:'234', c:0};
var defaultValue = 'defaultValue';
var attr = 'someNonExistAttribute';
emptyObj.hasOwnProperty(attr) ? emptyObj[attr] : defaultValue;//=> 'defaultValue'
attr = 'c'; // => 'c'
emptyObj.hasOwnProperty(attr) ? emptyObj[attr] : defaultValue; // => 0
In this way, even if the value of 'c' is 0, it will still get the correct value.
var obj = {
a: 2,
b: 4
};
console.log(obj);
--> {a: 2, b: 4}
function applyDefaults(obj) {
obj.a ||= 10;
obj.b ||= 10;
obj.c ||= 10;
}
// do some magic
applyDefaults(obj);
console.log(obj);
--> {a: 2, b: 4, c: 10}
This works because
undefined || "1111111" --> "1111111"
"0000000" || "1111111" --> "0000000"
as null, undefined, NaN, 0, "" (Empty String), false itself, are all considered to be equivalent to false (falsy). Anything else is true (truthy).
Note that this is not uniformly supported across browsers and nodejs versions (confirm for yourself).
So two troublesome cases are the empty String "" and 0 (zero). If it is important not to override those, you might need to rewrite this as:
if (typeof obj.d == "undefined") obj.d = "default"
This will be better supported across browsers also.
Alternatively you could write this as:
obj.d ??= "default"
This is the nullish assignment which applies only to values that are null or undefined (nullish) - of which the empty string is not part. However, this has again a diminished cross-browser support.
See also on the official Mozilla Website - Assigning a default value to a variable.
This is actually possible to do with Object.create. It will not work for "non defined" properties. But for the ones that has been given a default value.
var defaults = {
a: 'test1',
b: 'test2'
};
Then when you create your properties object you do it with Object.create
properties = Object.create(defaults);
Now you will have two object where the first object is empty, but the prototype points to the defaults object. To test:
console.log('Unchanged', properties);
properties.a = 'updated';
console.log('Updated', properties);
console.log('Defaults', Object.getPrototypeOf(properties));
Object.withDefault = (defaultValue,o={}) => {
return new Proxy(o, {
get: (o, k) => (k in o) ? o[k] : defaultValue
});
}
o = Object.withDefault(42);
o.x //=> 42
o.x = 10
o.x //=> 10
o.xx //=> 42
One approach would be to take a defaults object and merge it with the target object. The target object would override values in the defaults object.
jQuery has the .extend() method that does this. jQuery is not needed however as there are vanilla JS implementations such as can be found here:
http://gomakethings.com/vanilla-javascript-version-of-jquery-extend/
With the addition of the Logical nullish assignment operator, you can now do something like this
const obj = {}
obj.a ??= "default";
In the case where you have an empty list as the default value and want to push to it, you could do
const obj = {}
(obj.a ??= []).push("some value")
I came here looking for a solution because the header matched my problem description but it isn't what i was looking for but i got a solution to my problem(I wanted to have a default value for an attribute which would be dynamic something like date).
let Blog = {
title : String,
image : String,
body : String,
created: {type: Date, default: Date.now}
}
The above code was the solution for which i finally settled.
Wit this example object:
obj = {
id: '123',
attr: 'something'
}
Now I want to add the attribute link in the attribute data. Sometimes data is already existing, but in this example data doesn't exist.
So if I do
obj.data.link = 'www.website.com';
I get the error TypeError: Cannot set property 'link' of undefined.
Result should be:
obj = {
id: '123',
attr: 'something',
data: {
link: 'www.website.com'
}
}
You need to create the data object first:
obj.data = {};
obj.data.link = 'www.website.com';
or you can do it all at once:
obj.data = {
link: 'www.website.com'
};
if data may or may not already by there, there's a handy shortcut that will use the existing one if it's there or create a new one if not:
obj.data = obj.data || {};
obj.data.link = 'www.website.com';
That uses the JavaScript's curiously-powerful || operator.
You need to initialize the data property. You can do something like this:
var obj = {
id: '123',
attr: 'something'
};
obj.data = {};
obj.data.link = 'www.website.com';
In the case for the property existing you can check before assigning link.
if (!obj.data) {
obj.data = {};
}
And as stated in another answer you can use the or operator which I've heard is 'curiously powerful' =]
obj.data = obj.data || {};
That basically means if this value ( obj.data ) exists use it and if it doesn't use the right operand ( {} ). This works because of short circuit evaluation.
Javascript From 1.8.5 you can use the following method:
Object.defineProperty(obj, "data", {
value: {'link' : 'www.website.com'},
writable: true,
enumerable: true,
configurable: true
});
Good luck :)