How to use variable value directly in a dynamically created JavaScript getter? - javascript

I have the following code for adding getters to an object on the fly:
const obj = {};
const uni = { name1: ..., name2: ..., ...};
for(const nm in uni) {
const u = obj[nm] = {};
Object.defineProperty(u, 'value', {
configurable: true,
enumerable: true,
get: () => { return uni[nm] },
});
}
I want getters to return different values, so I want uni[nm] to be understood literary, i.e. the first getter would return uni.name1, the second uni.name2 etc.
How do I do that?
EDIT:
I want to dynamically create an object like this:
{
name1: { get value() { return uni.name1 }},
name2: { get value() { return uni.name2 }},
...
}

Don't use for..in. And you pretty much never need Object.defineProperty. That's all stone age JavaScript.
Instead use -
for..of to iterate over Object.keys.
get keyword to define a getter
const uni = { name1: "foo", name2: "bar" }
let obj = {}
for(const key of Object.keys(uni)) {
obj[key] = {
get value() {
return uni[key]
}
};
}
console.log(obj.name1.value) // "foo"
console.log(obj.name2.value) // "bar"
uni.name1 = "zzz"
console.log(obj.name1.value) // "zzz"
This can be expressed using array.reduce and -
object spread to extend the definition of an object
computed properties to define a dynamic key -
const uni = { name1: "foo", name2: "bar" }
const obj = Object.keys(uni).reduce(
(o, key) => {
return {...o, [key]: { get value() { return uni[key] }}} // extend
},
{} // initial object
)
console.log(obj.name1.value) // "foo"
console.log(obj.name2.value) // "bar"
uni.name1 = "zzz"
console.log(obj.name1.value) // "zzz"

You can achieve this by using the square bracket notation ([]) to dynamically access properties in the uni object, like this:

Related

Destructuring first value of an array of objects in reduce method

Let's say you an array of objects, where you're reducing a property into one result separated by dashes, e.g:
const array = [
{ foo: "foo" },
{ foo: "foo" },
]
Should become the string:
foo-foo
If you're using the reduce method, you might do something like this:
const array = [ { foo: "foo" }, { foo: "foo" }, { foo: "foo" } ];
const result = array.reduce( ( accumulator, { foo } ) => {
return accumulator + "-" + foo;
} );
console.log( result );
However, the problem is that the default initial value (the first element of the array) is the entire first object, naturally resulting in [object Object]-foo-foo.
Question is: is there a simple way to, for example destructure, the initial value?
You can bypass the issue by, for example, using an if-statement specifically checking whether the accumulator is currently an object:
const array = [ { foo: "foo" }, { foo: "foo" }, { foo: "foo" } ];
const result = array.reduce( ( accumulator, { foo } ) => {
if ( accumulator.hasOwnProperty( "foo" ) ) {
return accumulator.foo + "-" + foo;
}
return accumulator + "-" + foo;
} );
console.log( result );
However, I am interested in a simpler/prettier way of doing this, using less "arbitrary-looking" code, perhaps a way to do this using the actual initialValue argument of reduce.
Note: I am not looking for an answer to an actual real problem I am facing now (and thus not looking for alternative solutions such as for let/of loops or filtering the array), I am asking for the sake of learning more about the reduce method in these types of situations for future reference.
You could set the initial value to an empty string, and use a ternary to check if the string is empty, if true, then return only the foo, otherwise, return the accumulator, a dash, and foo:
const array = [ { foo: "foo" }, { foo: "foo" }, { foo: "foo" } ];
const result = array.reduce((a, {foo}) => a ? `${a}-${foo}` : foo, '');
console.log(result);
We can be sure that on the initial loop that the ternary will return only foo as '' evaluates to false.
You could map and join the items.
var array = [{ foo: "foo" }, { foo: "foo" }, { foo: "foo" }],
result = array
.map(({ foo }) => foo)
.join('-');
console.log(result);
A reduce approach with a check if a dash is necessary.
var array = [{ foo: "foo" }, { foo: "foo" }, { foo: "foo" }],
result = array.reduce((r, { foo }) => r + (r && '-') + foo, '');
console.log(result);

How to assign a nested property of an object given an array of keys [duplicate]

This question already has answers here:
How to set value to a property in a Javascript object, which is identified by an array of keys
(2 answers)
Closed 3 years ago.
I have an object that resembles this:
const obj = {
prop1: {
prop2: {
value: 'something',
...otherProps
}
},
...otherProps
}
And an array that looks like this:
const history = ['prop1', 'prop2', 'value']
How do I assign the property value of prop2 a new value in a way that would also work for any other depth.
Just loop through the property list and get each property of the object.
const obj = {
prop1: {
prop2: {
value: 'something'
}
}
};
const history = ['prop1', 'prop2', 'value'];
console.log(setPropertyValue(obj, history, "test"));
console.log(getPropertyValue(obj, history));
function getPropertyValueContainer(values, propertyList) {
var copy = propertyList.slice(), propertyName = copy.pop();
for (var property of copy) values = values[property];
return { propertyName: propertyName, values: values };
}
function getPropertyValue(values, propertyList) {
var container = getPropertyValueContainer(values, propertyList);
return container.values[container.propertyName];
}
function setPropertyValue(values, propertyList, value) {
var container = getPropertyValueContainer(values, propertyList);
return container.values[container.propertyName] = value;
}
You can use references.
So here i am taking reference of object in a variable untill i key equal to value and than adding value using that ref.
const obj = { prop1: { prop2: { value: 'something'}}}
const history = ['prop1', 'prop2', 'value']
let ref = obj;
history.forEach((e,index)=>{
if(e !== 'value' ) ref = ref[e]
})
ref.value = 'xyz'
console.log(obj)

Symbol.iterator: get all the object's properties in iterator object

I am currently reading about Symbols and Iterators (ES6 features) and after running into an example there, I am trying to make an object iterable so that I can use for...of feature. From few examples/answers here/articles I checked, it looks like this in plain case:
let obj = {
prop1: 5,
prop2: 'test',
prop3: new Date(),
[Symbol.iterator]: () => ({
items: obj.items,
next: function next() {
return {
done: this.items.length === 0,
value: this.items.shift()
}
}
})
};
Object.defineProperty(obj, "items", {
enumerable: false,
get: function() {
let props = [];
for(let prop in this) if(this.hasOwnProperty(prop)) props.push(this[prop]);
return props;
}
});
for(let prop of obj) console.log(prop);
But I find it annoying to list manually all the values of an object's properties in items array of iterator. Also it feels dirty and messy with Object.defineProperty. I am kind of trying tom expand the example from the link. Is there a smarter/simpler way to get all the object's properties inside iterator (instead of items: obj.items and related bloat or manually listing items like items: [5, 'test', new Date()])?
You could return a generator in the symbol iterator maybe:
[Symbol.iterator]:function*(){
for(value of Object.values(this)){
yield value;//may extend this to your needs
}
}
Or in your case:
[Symbol.iterator]:function*(){
var i=1;
while(this["prop"+i]){
yield this["prop"+i];//may extend this to your needs
i++;
}
}
http://jsbin.com/zekunalusi/edit?console
You should not use an items getter but instead just create the array inside the iterator method. Also you can use generator syntax to create the iterator, which is much easier.
But the simplest way to achieve what you want is
let obj = {
prop1: 5,
prop2: 'test',
prop3: new Date(),
[Symbol.iterator]() { return Object.keys(this).map(k => this[k])[Symbol.iterator](); }
};
function objectEntries(obj) {
let index = 0;
// In ES6, you can use strings or symbols as property keys,
// Reflect.ownKeys() retrieves both
let propKeys = Reflect.ownKeys(obj);
return {
[Symbol.iterator]() {
return this;
},
next() {
if (index < propKeys.length) {
let key = propKeys[index];
index++;
return { value: [key, obj[key]] };
} else {
return { done: true };
}
}
};
}
let obj = { first: 'Jane', last: 'Doe' };
for (let [key,value] of objectEntries(obj)) {
console.log(`${key}: ${value}`);
}
http://2ality.com/2015/02/es6-iteration.html

Destruct object with dynamic parameters

I'm trying to destruct object by passing params, but i can't achieve it the way i want. Param's are string's.
function(param){
const obj = {
foo: 'foo',
bar: 'bar'
}
const {[param], ...destructedObj} = obj; // where param === 'foo' || 'bar'
return obj;
}
You can use Object.keys to retrieve keys and then decide which key to use to selectively destruct the object at hand:
const obj = {
foo: 1,
bar: 2
};
const keys = Object.keys(obj);
const { [keys[0]]: x } = obj;
console.log(`${keys[0]}:${x}`);
But I'm not sure where you are going with this?

Remove value from object without mutation

What's a good and short way to remove a value from an object at a specific key without mutating the original object?
I'd like to do something like:
let o = {firstname: 'Jane', lastname: 'Doe'};
let o2 = doSomething(o, 'lastname');
console.log(o.lastname); // 'Doe'
console.log(o2.lastname); // undefined
I know there are a lot of immutability libraries for such tasks, but I'd like to get away without a library. But to do this, a requirement would be to have an easy and short way that can be used throughout the code, without abstracting the method away as a utility function.
E.g. for adding a value I do the following:
let o2 = {...o1, age: 31};
This is quite short, easy to remember and doesn't need a utility function.
Is there something like this for removing a value? ES6 is very welcome.
Thank you very much!
Update:
You could remove a property from an object with a tricky Destructuring assignment:
const doSomething = (obj, prop) => {
let {[prop]: omit, ...res} = obj
return res
}
Though, if property name you want to remove is static, then you could remove it with a simple one-liner:
let {lastname, ...o2} = o
The easiest way is simply to Or you could clone your object before mutating it:
const doSomething = (obj, prop) => {
let res = Object.assign({}, obj)
delete res[prop]
return res
}
Alternatively you could use omit function from lodash utility library:
let o2 = _.omit(o, 'lastname')
It's available as a part of lodash package, or as a standalone lodash.omit package.
With ES7 object destructuring:
const myObject = {
a: 1,
b: 2,
c: 3
};
const { a, ...noA } = myObject;
console.log(noA); // => { b: 2, c: 3 }
one line solution
const removeKey = (key, {[key]: _, ...rest}) => rest;
Explanations:
This is a generic arrow function to remove a specific key. The first argument is the name of the key to remove, the second is the object from where you want to remove the key. Note that by restructuring it, we generate the curated result, then return it.
Example:
let example = {
first:"frefrze",
second:"gergerge",
third: "gfgfg"
}
console.log(removeKey('third', example))
/*
Object {
first: "frefrze",
second: "gergerge"
}
*/
To add some spice bringing in Performance. Check this thread bellow
https://github.com/googleapis/google-api-nodejs-client/issues/375
The use of the delete operator has performance negative effects for
the V8 hidden classes pattern. In general it's recommended do not use
it.
Alternatively, to remove object own enumerable properties, we could
create a new object copy without those properties (example using
lodash):
_.omit(o, 'prop', 'prop2')
Or even define the property value to null or undefined (which is
implicitly ignored when serializing to JSON):
o.prop = undefined
You can use too the destructing way
const {remov1, remov2, ...new} = old;
old = new;
And a more practical exmple:
this._volumes[this._minCandle] = undefined;
{
const {[this._minCandle]: remove, ...rest} = this._volumes;
this._volumes = rest;
}
As you can see you can use [somePropsVarForDynamicName]: scopeVarName syntax for dynamic names. And you can put all in brackets (new block) so the rest will be garbage collected after it.
Here a test:
exec:
Or we can go with some function like
function deleteProps(obj, props) {
if (!Array.isArray(props)) props = [props];
return Object.keys(obj).reduce((newObj, prop) => {
if (!props.includes(prop)) {
newObj[prop] = obj[prop];
}
return newObj;
}, {});
}
for typescript
function deleteProps(obj: Object, props: string[]) {
if (!Array.isArray(props)) props = [props];
return Object.keys(obj).reduce((newObj, prop) => {
if (!props.includes(prop)) {
newObj[prop] = obj[prop];
}
return newObj;
}, {});
}
Usage:
let a = {propH: 'hi', propB: 'bye', propO: 'ok'};
a = deleteProps(a, 'propB');
// or
a = deleteProps(a, ['propB', 'propO']);
This way a new object is created. And the fast property of the object is kept. Which can be important or matter. If the mapping and the object will be accessed many many times.
Also associating undefined can be a good way to go with. When you can afford it. And for the keys you can too check the value. For instance to get all the active keys you do something like:
const allActiveKeys = Object.keys(myObj).filter(k => myObj[k] !== undefined);
//or
const allActiveKeys = Object.keys(myObj).filter(k => myObj[k]); // if any false evaluated value is to be stripped.
Undefined is not suited though for big list. Or development over time with many props to come in. As the memory usage will keep growing and will never get cleaned. So it depend on the usage. And just creating a new object seem to be the good way.
Then the Premature optimization is the root of all evil will kick in. So you need to be aware of the trade off. And what is needed and what's not.
Note about _.omit() from lodash
It's removed from version 5. You can't find it in the repo. And here an issue that talk about it.
https://github.com/lodash/lodash/issues/2930
v8
You can check this which is a good reading https://v8.dev/blog/fast-properties
As suggested in the comments above if you want to extend this to remove more than one item from your object I like to use filter. and reduce
eg
const o = {
"firstname": "Jane",
"lastname": "Doe",
"middlename": "Kate",
"age": 23,
"_id": "599ad9f8ebe5183011f70835",
"index": 0,
"guid": "1dbb6a4e-f82d-4e32-bb4c-15ed783c70ca",
"isActive": true,
"balance": "$1,510.89",
"picture": "http://placehold.it/32x32",
"eyeColor": "green",
"registered": "2014-08-17T09:21:18 -10:00",
"tags": [
"consequat",
"ut",
"qui",
"nulla",
"do",
"sunt",
"anim"
]
};
const removeItems = ['balance', 'picture', 'tags']
console.log(formatObj(o, removeItems))
function formatObj(obj, removeItems) {
return {
...Object.keys(obj)
.filter(item => !isInArray(item, removeItems))
.reduce((newObj, item) => {
return {
...newObj, [item]: obj[item]
}
}, {})
}
}
function isInArray(value, array) {
return array.indexOf(value) > -1;
}
My issue with the accepted answer, from an ESLint rule standard, if you try to destructure:
const { notNeeded, alsoNotNeeded, ...rest } = { ...ogObject };
the 2 new variables, notNeeded and alsoNotNeeded may throw a warning or error depending on your setup since they are now unused. So why create new vars if unused?
I think you need to use the delete function truly.
export function deleteKeyFromObject(obj, key) {
return Object.fromEntries(Object.entries(obj).filter(el => el[0] !== key))
}
with lodash cloneDeep and delete
(note: lodash clone can be used instead for shallow objects)
const obj = {a: 1, b: 2, c: 3}
const unwantedKey = 'a'
const _ = require('lodash')
const objCopy = _.cloneDeep(obj)
delete objCopy[unwantedKey]
// objCopy = {b: 2, c: 3}
For my code I wanted a short version for the return value of map() but the multiline/mutli operations solutions were "ugly". The key feature is the old void(0) which resolve to undefined.
let o2 = {...o, age: 31, lastname: void(0)};
The property stays in the object:
console.log(o2) // {firstname: "Jane", lastname: undefined, age: 31}
but the transmit framework kills it for me (b.c. stringify):
console.log(JSON.stringify(o2)) // {"firstname":"Jane","age":31}
I wrote big function about issue for me. The function clear all values of props (not itself, only value), arrays etc. as multidimensional.
NOTE: The function clear elements in arrays and arrays become an empty array. Maybe this case can be added to function as optional.
https://gist.github.com/semihkeskindev/d979b169e4ee157503a76b06573ae868
function clearAllValues(data, byTypeOf = false) {
let clearValuesTypeOf = {
boolean: false,
number: 0,
string: '',
}
// clears array if data is array
if (Array.isArray(data)) {
data = [];
} else if (typeof data === 'object' && data !== null) {
// loops object if data is object
Object.keys(data).forEach((key, index) => {
// clears array if property value is array
if (Array.isArray(data[key])) {
data[key] = [];
} else if (typeof data[key] === 'object' && data !== null) {
data[key] = this.clearAllValues(data[key], byTypeOf);
} else {
// clears value by typeof value if second parameter is true
if (byTypeOf) {
data[key] = clearValuesTypeOf[typeof data[key]];
} else {
// value changes as null if second parameter is false
data[key] = null;
}
}
});
} else {
if (byTypeOf) {
data = clearValuesTypeOf[typeof data];
} else {
data = null;
}
}
return data;
}
Here is an example that clear all values without delete props
let object = {
name: 'Semih',
lastname: 'Keskin',
brothers: [
{
name: 'Melih Kayra',
age: 9,
}
],
sisters: [],
hobbies: {
cycling: true,
listeningMusic: true,
running: false,
}
}
console.log(object);
// output before changed: {"name":"Semih","lastname":"Keskin","brothers":[{"name":"Melih Kayra","age":9}],"sisters":[],"hobbies":{"cycling":true,"listeningMusic":true,"running":false}}
let clearObject = clearAllValues(object);
console.log(clearObject);
// output after changed: {"name":null,"lastname":null,"brothers":[],"sisters":[],"hobbies":{"cycling":null,"listeningMusic":null,"running":null}}
let clearObject2 = clearAllValues(object);
console.log(clearObject2);
// output after changed by typeof: {"name":"","lastname":"","brothers":[],"sisters":[],"hobbies":{"cycling":false,"listeningMusic":false,"running":false}}

Categories