Adding new properties when working with spread objects? - javascript

I have some existing code that looks like this (I can't change the function definitions of add_from_address or email.send, only what's inside the body of add_from_address):
function add_from_address(...args) {
args.from = "foo#bar.com";
console.log(args);
email.send(...args).catch((error) => {
console.error("Failed", error);
});
}
This is the output of console.log(args):
[ 'subscription-created',
{ subject: 'hello', user_id: user_id },
from: "foo#bar.com" }];
But I was hoping to achieve this:
[ 'subscription-created',
{ subject: 'hello', user_id: user_id, from: "foo#bar.com" }];
Obviously, I could just manually add the new property to the second element of args as follows:
function add_from_address(...args) {
args[1].from = "foo#bar.com";
console.log(args);
email.send(...args).catch((error) => {
console.error("Failed", error);
});
}
But is there a more elegant way to add properties when working with spread operators?

Sinne the object you want to add the property to is the second argument (args[1]), you need to add the property to that object, not the array:
function add_from_address(...args) {
args[1] = {...args[1], from: "foo#bar.com"};
email.send(...args).catch((error) => {
console.error("Failed", error);
});
}
That updates your array with a new object, adding (or overwriting) the from property.
Live Example:
function add_from_address(...args) {
args[1] = {...args[1], from: "foo#bar.com"};
console.log(args);
}
add_from_address(
'subscription-created',
{ subject: 'hello', user_id: 42 }
);
In a comment you've asked why not to use:
args[1].from = "foo#bar.com";
The only issue with doing that is that it modifies the object that was passed into the method. Example:
function add_from_address1(...args) {
args[1] = {...args[1], from: "foo#bar.com"};
console.log(args);
}
function add_from_address2(...args) {
args[1].from = "foo#bar.com";
console.log(args);
}
const obj1 = { subject: 'hello', user_id: 42 };
add_from_address1('subscription-created', obj1);
console.log(obj1.from); // undefined, because the object doesn't have a `from` property
const obj2 = { subject: 'hello', user_id: 42 };
add_from_address2('subscription-created', obj2);
console.log(obj2.from); // "foo#bar.com", because `add_from_address2` modified it
.as-console-wrapper {
max-height: 100% !important;
}
Note how add_from_address1 didn't modify obj1, but add_from_address2 did modify obj2.
Modifying the object that was passed in may be fine in your case. In general, though, methods should leave the caller's objects alone unless the purpose of the method is to modify the object.
Just as a side note, if you want to you can use formal (named) parameters for the first two and rest for the...rest:
function add_from_address(type, msg, ...rest) {
email.send(type, {...msg, from: "foo#bar.com"}, ...rest)
.catch((error) => {
console.error("Failed", error);
});
}

Related

Check that the object has one property but not the other using toMatchObject in Jest

Let's say something returns a complex object:
const value = {
...,
client: { ... },
...
};
I want to test a bunch of properties, and for client I want to make sure that it has a certain name, but does not have id at all.
How do I implement it?
expect(value).toMatchObject({
...,
client: ???,
...
});
If it was just name I would have done client: { name: 'expected' }.
I tried client: { id: undefined, name: "expected" }, but undefined is not the same as "does not have a property".
I know I can do a separate case of expect(value).not.toHaveProperty('client.name'), but it would be great to just have one toMatchObject (I need this in multiple tests).
Sorted with this, but still keen to know if there is a better way:
expect.extend({
matchingAll(actual, expected) {
return { pass: expected.every(e => e.asymmetricMatch(actual)) };
}
});
expect(value).toMatchObject({
...,
client: expect.matchingAll([
expect.objectContaining({ name: 'expected' }),
expect.not.objectContaining({ id: expect.anything() }),
]),
...
});
clear the property undefined and after to compare
function clear(a){
Object.keys(a).forEach(key => {
if (a[key] === undefined) {
delete a[key];
}
});
}
let client = { id: undefined, name: "expected" }
clear(client )
console.log(client)

In sails use a "basemodel", derive from that and use baseclass functions?

To improve our program and reduce code redundancy, we wish to create some inheritance inside the models..
Now take a typical User model, it has a name and password field as "baseclass" and several subclasses can improve upon this depending in the specific application's needs.
So a baseuser would look like:
module.exports = {
attributes: {
username: {
type: 'string',
required: true,
unique: true
},
password: {
type: 'string',
required: true,
},
},
beforeCreate: async function(user, cb) {
const hash = await bcrypt.hash(user.password, 10);
user.password = hash;
cb();
},
}
This bare class doesn't correspond to any database table in its own. Now in derived class from this, VerifyableUser (a model for users that must have verification links), there are a few extra fields, one which is a verify url.
Now to "extend" classes lodash' _.merge function is used, as explained in this question .
const BaseUser = require("../../BaseUser.js");
module.exports = _.merge({}, BaseUser, {
attributes: {
verify_key: {
type: 'string'
}
},
beforeCreate: async function(user, cb) {
user.verify_key = 'helloworld'; //crypto used to generate...
cb();
}
};
Now the problem should be obvious, the derived class' beforeCreate overwrites the original beforeCreate: in a normal OO environment this isn't a big problem either, as I could just call Base.beforeCreate() or something similar.
However can something be done using lodash' merge? Or should I use another way to extend objects? (Or do I really have to repeat myself and retype the beforeCreate?).
or something similar:
// VerifyableUser
async beforeCreate(user, cb) {
await BaseUser.beforeCreate(user, () => 0);
//...
}
You could also use _.mergeWith to check what each property being merged is and if it is a function just pick the object and not the source (in your case the source is BaseUser):
const BaseUser = require("../../BaseUser.js");
let obj = {
attributes: {
verify_key: {
type: 'string'
}
},
beforeCreate: async function(user, cb) {
user.verify_key = 'helloworld'; //crypto used to generate...
cb();
}
}
module.exports = _.mergeWith(
obj,
BaseUser,
(objValue, srcValue, key, object, source) => _.isFunction(objValue) ? objValue : _.merge(object[key], source[key])
)
Here is a test:
var data = {
food: "chicken",
foo: () => console.log('chicken!')
}
var source = {
prop1: 1,
prop2: 1,
foo: () => console.log('foo!')
}
var result = _.merge(data, source, (objValue, srcValue, key, object, source) => _.isFunction(objValue) ? objValue : _.merge(object[key], source[key]))
console.log(result)
result.foo()
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>

Access Observable values and assign to a collection

I am receiving an Observable back from an Angular service and I can see the data coming back from the service when it resolves using .subscribe
returnedObservable$.subscribe(res => {
console.log(res);
return res;
]);
I have tried assigning this to an Object inside of the .subscribe but the assignment isn't happening.
let newObj = {};
returnedObservable$.subscribe(res => newObj = res);
How can I make this assignment to get the values out of the Observable?
The first time it tries to access the response the result is null but eventually resolves.
The console.log(res) looks like this:
{
itemList: [
{ id: 1, name: Steve },
{ id: 2, name: Bob },
{ id: 3, name: Tim }
]
}
You can use it like this:
let newObj = {};
returnedObservable$.subscribe(res => {newObj = res}).add(()=>
{
//Use your object here
console.log(newObj);
});

spread syntax with map doesn't work

data.map(obj =>
{person_name: obj.user.name,
...obj})
Why above code failed?
my data look like this
[{user:{name:'hello'},age:1},{user:{name:'world'},age:1,{user:{name:'another_name'},age:1]
I want to 'pull' the obj.user.name out from the nested obj.
Try this:
data.map(obj => ({person_name: obj.user.name,...obj}))
The { at the beginning of the object was interpreted as the beginning of a block instead of an object.
You can read more about this on MDN
EDIT:
As Pawel mentioned, using the spread operator with objects does not work with pure es6 you need to use a specific transform like in this proposal: https://github.com/tc39/proposal-object-rest-spread
You can't spread an object like this (throws SyntaxError at ....
I would write it using Object.assign:
data.map(obj => Object.assign({person_name: obj.user.name}, obj ));
With babel, you could use rest syntax (parameters) ... for getting the rest of the object and store name property directly.
var data = [{ user: { name: 'hello' }, age: 1 }, { user: { name: 'world' }, age: 1 }, { user: { name: 'another_name' }, age: 1 }];
console.log(data.map(({ user, ...obj }) => Object.assign(obj, { person_name: user.name })));
.as-console-wrapper { max-height: 100% !important; top: 0; }
First of all, if you want to create an object using an arrow function, you have to wrap the curly braces in parenthesis:
data.map(obj => ({ foo: 'bar' }));
Otherwise, JS thinks the curly braces are the function body, and you get a syntax error.
And secondly, you can't use the spread operator in object literals. You can only use it when calling a function, using the array literal or when destructuring.
You can use Array.prototype.map() and directly return the desired object:
const data = [{user: {name: 'hello'},age: 1}, {user: {name: 'world'},age: 1},{user: {name: 'another_name'},age: 1}];
const result = data.map(obj => {
return {
person_name: obj.user.name,
age: obj.age
};
});
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

ES6 Default Parameters in nested objects

I want to have a function with default parameters inside nested objects, and I want to be able to call it either f() or specifying only individual parameters.
// A function with nested objects with default parameters:
function f({ a = 1, callback = ({ name, param } = { name: "qwe", param: 123 }) } = {}) {
console.log("a:", a);
console.log("callback:", callback);
}
// And I want to run it like this:
f();
f({ callback: { params: "456" } });
// But 'callback.name' becomes undefined.
When destructuring is mixed with default parameters, I admit the code is hard to read and write (especially when there are nested objects...).
But I think you are trying to do that:
function f({callback: {name = "cbFunction", params = "123"} = {}} = {}) {
console.log(name);
console.log(params);
}
f();
f({callback: {params: '789'}});
I found none of the answers here to be what he wanted. But it IS actually possible in a somewhat sexy way by doing this:
(EDIT: Simplified syntax and also show how to add default values for subobjects)
function f({
a = 1,
callback = {}
} = {}) {
callback = { // default values
name: "cbFunction",
params: "123",
...callback // overwrites it with given values
}
// do the same for any subobjects
callback.subObject = {
arg1: 'hi',
arg2: 'hello',
...callback.subObject
}
console.log("a:", a)
console.log("callback:", callback)
}
f()
f({a: 2, callback: {params: '789', subObject: {arg2: 'goodbye'}}})
Turned out to call it like this solves the problem, but is it the best way?
function f({
a = 1,
callback = ({
name,
param
} = {
name: "qwe",
param: 123
})
} = {}) {
console.log("a:", a);
console.log("callback:", callback);
}
f();
f({ callback: { name, params: "456" } });
Answer by #Badacadabra is nearly correct but missing the other top level parameter specified in the question.
function f({a = 1, callback: {name = "qwe", params = "123"} = {}} = {}) {
console.log(a);
console.log(name);
console.log(params);
}
However note that within the function body the properties of callback are addressed without the containing object. You could reconstitute them into such an object if you wanted with the line:
const callback = { name, params }
Either way, from the invocation point this works to fill in all missing values from all levels such as:
f({a: 2})
f({a: 2, callback: { name: "abc"}})
f({a: 2, callback: { params: "456" }})
etc.
EDIT
In response to Joakim's comment:
TotalAMD also said in a comment that "I want to use several nested objects with same fields name". So if he tries that approach with callback1 and callback2 as arguments then he would have to use different field names in them.
I missed that original requirement. One way to maintain the desired, duplicated nested names within the function interface would be to alias them within the scope of the function, as follows:
function f({
a = 1,
callback1: {name: name1 = "abc", params: params1 = "123"} = {},
callback2: {name: name2 = "def", params: params2 = "456"} = {},
} = {}) {
console.log(a);
console.log(name1);
console.log(params1);
console.log(name2);
console.log(params2);
}
You can then call the function with the designed interface and expected results:
f ({ callback1: { name: "One" }, callback2: { name: "Two" } })
Caveat: Whilst technically possible and potentially useful, this could get messy at deeper nesting levels. It might then be worth looking for an alternative interface design with less indirection.

Categories