ES6, parameter destructuring with default values. A way to write less - javascript

Ho can I avoid to write all fields with = ""?
const defaultPlayer = {
name: "",
surname: "",
age: "",
skill: ""
}
// ...
mapPropsToValues = ({ player }) => player || defaultPlayer
Is there in javascript that I can use to avoid write all the time = ""?
I mean, if I already know that defalut value of every field is "" (empty string) how can I do instead of write every field explicitly?

You can accomplish this using a with block and a Proxy object and eval:
let defaultPlayer;
with (new Proxy({}, {
has(o, key) {
try { eval(key); }
catch (e) { return true; }
},
get() {
return '';
}
})) {
defaultPlayer = {
userName,
age,
surname,
isAdmin: false
};
}
console.log(defaultPlayer);
When you omit a property value, it looks for a variable with the same name as the key. For example, {x} is the same as {x: x}. We can use the with() statement to have JavaScript check for properties of an object before looking for variables. Instead of a normal object, we'll define a Proxy that returns '' for any variables that it doesn't see in the local environment (by testing for an exception when evaling their name).
☠️ This is an obscene hack with nasty edge cases. Never use it. ☠️

Related

Is using undefined as a value in object definition a good practice?

tl;dr: Is it a good practice to use undefined as a value or should I avoid it and try with another aproach?
I have an object which I use as a schema for my two functions createUser() and updateUser() and based on what values I need, I reconfigure it.
For updateUser() I need to send only the keys user entered in a form so the only way I know of, without changing the structure of the object manually, is to set the values to undefined.
// d is passed as argument
const args = {
variables: {
where: {id: "someValue"},
data: {
username: d.username || undefined,
password: d.password || undefined,
role: d.role || undefined,
},
},
};
Now if I have entered only username, my object will be
variables: {
where: { id: "someValue"},
data: { username: "anotherValue" }
}
I have given it a second thought after ESLint gave me a warning "Unexpected use of undefined."
NOTE I can't send empty values to API. It has to have either value or not send the key at all.
const args = {
variables: {
where: {id: "someValue"},
data: {
username: d.username || "",
password: d.password || "",
role: d.role || "",
},
},
};
It makes more sense to use empty values instead of keeping them undefined, or you can use null.
So, that your API contracts won't get violated.
It's difficult to determine what good is and isn't, because it's always all about the needs and preferences of the client and your teammates.
The very simple and short answer to the question is: yes. undefined is a valid value and if this would be an evidently bad practice, then the language would not allow that value to be assigned. However, it's important to make sure that you do not duplicate your values. Taking a look at this object
{
variables: {
where: {id: "someValue"},
data: {
username: d.username || undefined,
password: d.password || undefined,
role: d.role || undefined,
},
},
};
we see that you repeat the same idea over and over again. Instead, you would do better to implement something like this:
function nicify(object) {
for (var key in object) {
if (!object[key]) object[key] = undefined;
else if ((typeof(object[key]) === "object") || (Array.isArray(object[key]))) {
nicify(object[key]);
}
}
}
the function above recursively does what you wanted to do with your attributes. This will be very helpful if you have many attributes and/or many use-cases. Also, if you consistently have the pattern of having a source object as in your example, then you can implement something like this:
function fillBySource(object, source) {
for (var key in source) {
object[key] = source[key] || undefined;
}
}
I think || null or || '' are better practise, if you JSON.stringify() to exchange data with a server or something nothing in the JSON tell you that a username and a password should be present in the data prop. You can see that in the following eg :
function test(username, password, role) {
const args = {
variables: {
where: {id: "someValue"},
data: {
username: username || undefined,
password: password || undefined,
role: role || undefined,
},
},
};
return JSON.stringify(args);
}
let json = test();
console.log(json);
To my best of knowledge, you should not assign undefined the way you are currently doing it, because if d.username in the code is not set, then it's value is already undefined.
Usually the use case for assigning undefined is when a variable is initially set, but you want to unset/reset it as if the value was never set in the first place.

Typescript - if conditrional inside a map

I am mapping a subset of user data to an object of a refined data set. Inside the map i want to check if a variable is null or undefined, and if yes, then to set this variable to a placeholder value.
The issue I am facing is that declaring an if statement inside the map is causing an error, but even though a map can have an index as a parameter, how can we use it functionally with a conditional Statement? Any insight most appreciated.
return this.UserService.search(url)
.map((data) => {
console.log(data);
data.result = <any> data.result.map((user, index) => ({
// if statement causing error here
if(user.name === null || user.name === undefined){
// error with this if condition
},
id: user.id,
name: user.name,
type: user.type,
password: user.password,
}));
return data;
}).map(data => ({
meta: { totalItems: data.size },
data: data.result,
}));
You're attempting to use an object literal as the return type, but naturally, an if statement (or any statement) can't be inside object literal syntax.
So instead, define a function body, which also uses curly braces, and put your code inside with an explicit return statement.
// Starts function body instead of single expression-v
data.result = <any> data.result.map((user, index) => {
if (some_condition) {
return "some value"; // The object?
} else {
return "other value"; // A different object?
}
/*
// I assume these are to be used in the object you return
id: user.id,
name: user.name,
type: user.type,
password: user.password,
*/
});
You can express conditions in literal maps, but it is somewhat ugly.
return {
a: 1,
...(some_condition && {
b: 1,
})
};
As far as i know you can't do that with JUST a map.
however you could follow it up with a filter() function:
const newArray = oldArray.map((value, index) => condition ? value : null).filter(v => v);
you basicaly iterate over each item and then return the value or null depending on your condition.
Now once you have the map you just filter it by removing the null values from the array.
Notice that the original array is not altered and a new one is returned.
thanks for the idea #user8897421 for the idea. i just wanted to turn it into a one liner.

SimpleSchema match any type but null

I'm planning to make a collection to hold different app-wide settings, like, say, amount of logged in users today, Google analytics tracking ID, etc. So I made a schema like this:
options_schema = new SimpleSchema({
key: {
type: String,
unique: true
},
value: {
},
modified: {
type: Date
}
});
Now the main problem is that I want value to be of any type: Number, String, Date, or even custom Objects. Though it has to be present, can't be null.
But of course it gets angry about not specifying the type. Is there a workaround for this?
You can use Match patterns for your fields' type which allow you to do pretty much anything :
const notNullPattern = Match.Where(val => val !== null)
value : {
type : notNullPattern
}
(See Arrow functions)
Note that this will allow everything but null, including undefined.
Defining patterns this way allow you to use them everywhere in your application including in check :
check({
key : 'the key',
modified : Date.now(),
value : {} // or [], 42, false, 'hello ground', ...
}, optionsSchema)
Match.test(undefined, notNullPattern) //true
Match.test({}, notNullPattern) //true
Match.test(null, notNullPattern) //false
A more general solution to exclude one value would simply be:
const notValuePattern =
unwantedValue => Match.Where(val => val !== unwantedValue))
The use of which is similar to the above:
Match.test(42, notValuePattern(null)) // true
Note that due to the use of the identity operator === it will notably fail for NaN:
Match.test(NaN, notValuePattern(NaN)) // true :(
A solution could be:
const notValuePattern =
unwantedValue => Match.Where(val => Number.isNaN(unwantedValue)?
!Number.isNaN(val)
: val !== unwantedValue
)
Should you want a solution to exclude some specific values in a schema (kind of the contrary of Match.OneOf), you could use the following:
const notOneOfPattern = (...unwantedValues) =>
Match.Where(val => !unwantedValues.includes(val)
)
This uses Array.prototype.includes and the ... spread operator. Use as follow:
Match.test(42, notOneOfPattern('self-conscious whale', 43)) // true
Match.test('tuna', notOneOfPattern('tyranny', 'tuna')) // false
Match.test('evil', notOneOfPattern('Plop', 'kittens')) // true
const disallowedValues = ['coffee', 'unicorns', 'bug-free software']
Match.test('bad thing', notOneOfPattern(...disallowedValues)) // true

How to check "truthyness" of an object, and then delete the falsy ones and edit the object

I need to take this object, check each properties truthyness, and then remove the untruthy ones.
var user = {
name: 'my name',
email: null,
pwHash: 'U+Ldlngx2BYQk',
birthday: undefined,
username: 'myname33',
age: 0
}
Here is the code I was trying
function truth(x) {
if (x) {
console.log("truthy");
} else {
delete;
}
}
for (x in user) {
truth(user[x]);
}
but it's not working and I'm not even sure I fully understand how to make sure I'm checking truthy right. what am I doing wrong?
You have to specify both the object and the property when you delete it.
It's not enough with just the value copied from the property that you pass to the function, so you can't do it in that function (unless you also pass in the object reference and the property name).
for (x in user) {
if (user[x]) {
console.log("truthy");
} else {
delete user[x];
}
}

Why can't I delete a mongoose model's object properties?

When a user registers with my API they are returned a user object. Before returning the object I remove the hashed password and salt properties. I have to use
user.salt = undefined;
user.pass = undefined;
Because when I try
delete user.salt;
delete user.pass;
the object properties still exist and are returned.
Why is that?
To use delete you would need to convert the model document into a plain JavaScript object by calling toObject so that you can freely manipulate it:
user = user.toObject();
delete user.salt;
delete user.pass;
Non-configurable properties cannot be re-configured or deleted.
You should use strict mode so you get in-your-face errors instead of silent failures:
(function() {
"use strict";
var o = {};
Object.defineProperty(o, "key", {
value: "value",
configurable: false,
writable: true,
enumerable: true
});
delete o.key;
})()
// TypeError: Cannot delete property 'key' of #<Object>
Another solution aside from calling toObject is to access the _doc directly from the mongoose object and use ES6 spread operator to remove unwanted properties as such:
user = { ...user._doc, salt: undefined, pass: undefined }
Rather than converting to a JavaScript object with toObject(), it might be more ideal to instead choose which properties you want to exclude via the Query.prototype.select() function.
For example, if your User schema looked something like this:
const userSchema = new mongoose.Schema({
email: {
type: String,
required: true,
},
name: {
type: String,
required: true
},
pass: {
type: String,
required: true
},
salt: {
type: String,
required: true
}
});
module.exports = {
User: mongoose.model("user", userSchema)
};
Then if you wanted to exclude the pass and salt properties in a response containing an array of all users, you could do so by specifically choosing which properties to ignore by prepending a minus sign before the property name:
users.get("/", async (req, res) => {
try {
const result = await User
.find({})
.select("-pass -salt");
return res
.status(200)
.send(result);
}
catch (error) {
console.error(error);
}
});
Alternatively, if you have more properties to exclude than include, you can specifically choose which properties to add instead of which properties to remove:
const result = await User
.find({})
.select("email name");
The delete operation could be used on javascript objects only. Mongoose models are not javascript objects. So convert it into a javascript object and delete the property.
The code should look like this:
const modelJsObject = model.toObject();
delete modlelJsObject.property;
But that causes problems while saving the object. So what I did was just to set the property value to undefined.
model.property = undefined;
Old question, but I'm throwing my 2-cents into the fray....
You question has already been answered correctly by others, this is just a demo of how I worked around it.
I used Object.entries() + Array.reduce() to solve it. Here's my take:
// define dis-allowed keys and values
const disAllowedKeys = ['_id','__v','password'];
const disAllowedValues = [null, undefined, ''];
// our object, maybe a Mongoose model, or some API response
const someObject = {
_id: 132456789,
password: '$1$O3JMY.Tw$AdLnLjQ/5jXF9.MTp3gHv/',
name: 'John Edward',
age: 29,
favoriteFood: null
};
// use reduce to create a new object with everything EXCEPT our dis-allowed keys and values!
const withOnlyGoodValues = Object.entries(someObject).reduce((ourNewObject, pair) => {
const key = pair[0];
const value = pair[1];
if (
disAllowedKeys.includes(key) === false &&
disAllowedValues.includes(value) === false
){
ourNewObject[key] = value;
}
return ourNewObject;
}, {});
// what we get back...
// {
// name: 'John Edward',
// age: 29
// }
// do something with the new object!
server.sendToClient(withOnlyGoodValues);
This can be cleaned up more once you understand how it works, especially with some fancy ES6 syntax. I intentionally tried to make it extra-readable, for the sake of the demo.
Read docs on how Object.entries() works: MDN - Object.entries()
Read docs on how Array.reduce() works: MDN - Array.reduce()
I use this little function just before i return the user object.
Of course i have to remember to add the new key i wish to remove but it works well for me
const protect = (o) => {
const removes = ['__v', '_id', 'salt', 'password', 'hash'];
m = o.toObject();
removes.forEach(element => {
try{
delete m[element]
}
catch(O_o){}
});
return m
}
and i use it as I said, just before i return the user.
return res.json({ success: true, user: await protect(user) });
Alternativly, it could be more dynamic when used this way:
const protect = (o, removes) => {
m = o.toObject();
removes.forEach(element => {
try{
delete m[element]
}
catch(O_o){}
});
return m
}
return res.json({ success: true, user: await protect(user, ['salt','hash']) });

Categories

Resources