How to override object get fallback - javascript

I'm trying to create an object that returns the property name for any property that was accessed. Is this possible to do cleanly in javascript / nodejs? This is what I would like to accomplish:
const mirror = {/*...*/};
console.log(mirror[1]);
// => 1
console.log(mirror.123);
// => '123'
console.log(mirror.randomPropertyHere);
// => 'randomPropertyHere'
I can overwrite a getter for a specific property, but I don't know how to do it generically. Also how can I differentiate between a number and a string?
My (not working) attempts
const mirror = {
get[1] () {
console.log('number');
return 1;
},
get['1'] () {
console.log('string');
return '1';
}
};
console.log(mirror[1]);
console.log(mirror['1']);
Very much appreciate your time and help!

With a proxy.
const mirror = new Proxy({}, {
get(_, prop) { return prop; }
});
console.log(mirror[1]);
// => 1
console.log(mirror['123']);
// => '123'
console.log(mirror.randomPropertyHere);
// => 'randomPropertyHere'
Also how can I differentiate between a number and a string?
Can't, object properties have to be strings or symbols.

Related

Can not read property of array element in javascript

Im fairly new to javascript and im trying to find an object of an array i created by the configID property. I used the method find() for this.
JS Code:
var configurationArray = flow.get("configurationArray") || [];
var configurationId = msg.topic.split("/")[1];
var configuration = {
configID: this.configurationID,
configurationModules: this.MSGesture.payload
};
if(!configurationArray.find(x => x.configID == this, configurationId)){
configurationArray.push(this.configuration);
} else {
//to do
}
I am using node-red which gives me flow and msg.
The Error i get:
Cannot read property 'configId' of undefined
Any help is appreciated
You could destructure the property and add a default object.
Then take some instead of find, because you need only the check.
At last, omit this and take directly the value.
if (!configurationArray.some(({ configID } = {}) => configID === configurationId)) {
configurationArray.push(this.configuration);
} else {
//to do
}
If you like to have an abstract callback, you could take a closure over configurationId, like
const hasId = id => ({ configID } = {}) => configID === id;
if (!configurationArray.some(hasId(configurationId)) {
configurationArray.push(this.configuration);
} else {
//to do
}

Remove an object's key and value using a variable from function

Hey I'm trying to remove a key:value pair from state inside a Javascript Object.
It works when I hardcode the key name in the code, but when I try to use a variable from a function call, it does nothing.
Can somebody help me out?
Here's an object example:
toppingsSelected: {
"Onion":"true",
"Mushrooms":"true",
}
This works, hardcoded:
deleteTopping = toppingName => {
const { Onion, ...withoutOnion } = toppingsSelected;
console.log(withoutOnion); // Returns object without onion
};
This doesn't work:
deleteTopping = toppingName => {
const toppingName = "Onion"; // Variable gets passed in
const { toppingName, ...withoutOnion } = toppingsSelected;
console.log(withoutOnion); // Returns original object, no change made
};
So I'm basically trying to remove a key from React state but I'm pretty new to Javascript.
How can I make Javascript aware that toppingName is a key?
Another option is to add square brackets arround toppingName, and assign it to a variable. As #Bergi pointed out in the comments, this option does not mutate toppingsSelected
const toppingsSelected = {
"Onion":"true",
"Mushrooms":"true",
};
const toppingName = "Onion";
const {
[toppingName]: topping,
...withoutOnion
} = toppingsSelected;
console.log(JSON.stringify(withoutOnion));
To set the React state, you'd then do this
this.setState({ toppingsSelected: withoutOnion })
You can use delete e.g.
delete toppingsSelected[toppingName];
One way of doing this is using Array.prototype.filter()
const _obj = {
'Onion': true,
'notOnion': false
};
const newObj = Object.keys(_obj)
.filter(key => key !== 'Onion')
.reduce((acc, cur) => ({ ...acc, cur }), {})
console.log(newObj); // { notOnion: false }
This will return a new object without the 'Onion' property

Defining an indexer for an object

One can make an object iterable by implementing [Symbol.iterator].
But how can one override the behavior of the [] operator?
For example i have a an object which has an array inside of it and i want to be able to access that given an index like obj[3].
is that possible?
example
const SignalArray = (data = []) => {
...
return {
add,
remove,
onAdd,
onRemove,
onSort,
removeOnAdd,
removeOnRemove,
removeOnSort,
[Symbol.iterator]() {
return {
next: () => {
if (index < data.length) {
return { value: data[index++], done: false };
} else {
index = 0;
return { done: true };
}
}
}
}
}
}
how can one override the behavior of the [] operator?
Only via Proxy, added in ES2015. You'd provide a get trap and handle the property keys you want to handle.
Here's an example where we check for property names that can be successfully coerced to numbers and return the number * 2:
const o = new Proxy({}, {
get(target, prop, receiver) {
const v = +prop;
if (!isNaN(v)) {
return v * 2;
}
return Reflect.get(...arguments);
}
});
o.x = "ex";
console.log(o[2]); // 4
console.log(o[7]); // 14
console.log(o.x); // "ex"
If you want to override setting the array element, you'd use set trap. There are several other traps available as well. For instance, in a reply to a comment, you said:
...if you hate es6 classes and want to write a wrapper around an array that gives it extra functionality, like an observable array for example...
...that would probably involve a set trap and overriding various mutator methods.

Shortest way to set value to variable in Knockout

In Knockout I have observable variable location. It is of type LocationEdit. This viewModel has observable and not fields.
I have collection of field names : fields. For each field I want to reset values for location
fields.forEach(field => {
if (this.uniqueField(locs, field)) {
if (ko.isObservable(this.location()[field])) {
this.location()[field](locs[0][field]);
} else {
this.location()[field] = locs[0][field];
}
}
});
To make this code more simpler (remove if-clauses), Can I somehow set value to this.location()[field] in one line?
You could use the conditional operator (... ? ... : ... ;) although it doesn't change much:
fields.forEach(field => {
if (this.uniqueField(locs, field)) {
ko.isObservable(this.location()[field]) ? this.location()[field](locs[0][field]) : this.location()[field] = locs[0][field];
}
});
Or you could write a function:
function upd(arr, index, val) {
ko.isObservable(arr[index]) ? arr[index](val) : arr[index] = val;
}
Usage:
fields.forEach(field => {
if (this.uniqueField(locs, field)) {
upd(this.location(), field, locs[0][field]);
}
});
See demo.
You could even add this function to ko:
if(typeof ko.updatePotentialObservable == 'undefined')
ko.updatePotentialObservable = function (arr[index], val) {
ko.isObservable(obj) ? arr[index](val) : arr[index]= val;
}
Usage:
fields.forEach(field => {
if (this.uniqueField(locs, field)) {
ko.updatePotentialObservable(this.location(), field, locs[0][field]);
}
});
See other demo
To be honest, I think Gôtô's answers are definitely your best options. Basically, you'd want to create a utility function similar to ko.unwrap but setting a value.
But since you said "also want to find another solution", here's a different utility function. I think the most confusing part of your code is the returning calls to locs[0][field] and this.location()[field]. I'd want something with this signature:
reset(source, target, keys);
So, in your code, you could do:
reset(
this.location(),
locs[0],
fields.filter(f => this.uniqueField(locs, f))
);
Now, writing this method, I ended up with this:
const mergePropsObs = (function() {
// Return a method for setting a specific property in object
const getSetter = obj => prop => ko.isObservable(obj[prop])
? obj[prop]
: val => obj[prop] = val;
// Return unique keys for two objects
// (I went with a quick oneliner; there are many ways to do this)
const allKeys = (obj1, obj2) =>
Object.keys(Object.assign({}, obj1, obj2));
return (base, ext, onlyProps) => {
const props = onlyProps || allKeys(base, ext);
const values = props.map(p => ko.unwrap(ext[p]));
props
.map(getSetter(base))
.forEach((setter, i) => setter(values[i]));
};
}());
var base = { a: 1, b: ko.observable(2), c: 5 };
mergePropsObs(
base,
{ a: 2, b: 3 },
["a", "b"]);
console.log(base.a);
console.log(base.b());
console.log(base.c);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
This utility method can be used with the signature mentioned above. It also has a fallback for when you don't provide an array of field names.

Copy object's attributes without having to repeat all the variable names?

I have an object and I'd like to change some of its attributes' names from mixedCase to snake_case without modifying the original object, and without having to re-write all the variable names twice. This example only includes two variables, but in my real code there are 10+ of them and it feels silly to repeat it all just to get two mixedCase variables into snake_case:
function do_stuff(data) {
const { totalScore, position } = data;
const params = { total_score: totalScore, position };
return do_more_stuff(params);
}
You could use these functions:
function snakeCase(s) {
return s.replace(/([A-Z])/g, c => '_' + c.toLowerCase());
}
function snakeCaseKeys(obj) {
return Object.keys(obj).reduce( (acc, key) =>
Object.assign(acc, { [snakeCase(key)]: obj[key] }), {} );
}
// Sample use:
const params = snakeCaseKeys({totalScore: 1, position: 2});
console.log(params);

Categories

Resources