Better way to create an object from an object of properties? - javascript

In my app, I'm creating several different objects using data from an API request as follows:
const newRelationship = new Relationship(
data.id,
data.account_id,
data.name,
data.description,
data.created_at,
data.created_by,
data.deleted_at,
data.deleted_by,
data.updated_at,
data.updated_by,
);
This feels a bit cumbersome. Is there a better (one line) way to do this, rather than writing out all the parameters by hand like this?
I'm hoping for something like below but I'm not 100% on spread/destructuring yet.
const newRelationship = new Relationship(...data);
My Relationship constructor is as follows:
constructor(id, accountId, name, description, createdAt, createdBy, updatedAt, updatedBy, deletedAt, deletedBy) {
this.id = id || '';
this.accountId = accountId || '';
this.name = name || '';
this.description = description || '';
this.createdAt = createdAt || '';
this.createdBy = createdBy || '';
this.deletedAt = deletedAt || '';
this.deletedBy = deletedBy || '';
this.updatedAt = updatedAt || '';
this.updatedBy = updatedBy || '';
}

Simplify your constructor to:
const defaults = { id: "", name: "", /*...*/ };
//...
constructor(options) {
Object.assign(this, defaults, options);
}
Then you can just do:
new Relationship(data)

If you are writing the class/function (opinion) I would go with object destructuring in the parameters, it makes explicit that you are passing an object to the function and enumerates the properties (you can also rename them if you'd like in the destructure statement). If you are using code that can't be updated on your end I would suggest using array spread with Object.values()
class Relationship {
constructor({ id, account_id, name, description, created_at, created_by, deleted_at, deleted_by, updated_at, updated_by }) {
// do stuff with your variables
console.log(`ID is ${id}`);
}
}
class OtherRelationship {
constructor(id, account_id, name, description, created_at, created_by, deleted_at, deleted_by, updated_at, updated_by) {
// do stuff with your variables
console.log(`ID is ${id}`);
}
}
const dummyParams = {
id: 1,
account_id: 1,
name: 'test',
description: 'tesssst',
created_at: 'some date',
created_by: 1,
deleted_at: 'some date',
deleted_by: 1,
updated_at: 'some date',
updated_by: 1,
}
const newRelationship = new Relationship(dummyParams);
const newOtherRelationship = new OtherRelationship(...Object.values(dummyParams))

Related

Promise not getting resolved inside reduce array method javascript

I have large array of objects and filtered the objects based on the userID. Here is the code below.
const filteredArr = LargeArr.Items.reduce(
async(acc, { attributes: { dob, name, picture} = { dob: null, name: null, picture: null }, userID }) => {
let pic = null;
if (picture) { pic = await getPic(picture); } // async here
acc[userID] = { name, userID, pic, dob };
return acc;
}, {});
Expected Output :
{
'1595232114269': {
name: 'Mark Status',
userID: '1595232114269',
picture: 'mark-status.jpg',
dob: '2020-08-10'
},
'48e69555d778f9b9a3a1d553b9c3b8f7dd6a3394ac82df1433b60a69c055d23d': {
name: 'Jack Thomas',
userID: '48e69555d778f9b9a3a1d553b9c3b8f7dd6a3394ac82df1433b60a69c055d23d',
picture: 'jack-thomas.jpg',
dob: '1990-12-20'
},
'48e69555d778f9b9a3a1d553b9c3b8f7dd6a3394ac82df1433b60a69c055d47p': {
name: 'Petro Huge',
userID: '48e69555d778f9b9a3a1d553b9c3b8f7dd6a3394ac82df1433b60a69c055d47p',
picture: 'petro huge.jpg',
dob: '1856-12-20'
},
'48e69555d778f9b9a3a1d553b9c3b8f7dd6a3394ac82df1433b60a69c055d55j': {
name: 'Mark Henry',
userID: '48e69555d778f9b9a3a1d553b9c3b8f7dd6a3394ac82df1433b60a69c055d55j',
picture: 'mark-henry.jpg',
dob: '2005-12-29'
}
}
I need to get picture from an api which is asynchronous, so used async await inside the reduce method. The problem here is it is always showing as Promise pending. If this was an array of object, then i can return Promise.all, but since this is object containing object how can i proceed with this inside reduce method? I need the exact same expected output.
Can somebody help me with this? Any help would be really appreciated.
To use reduce while iterating over items asynchronously, you'd have to have the accumulator which gets passed from callback to callback to be a Promise. While this is possible, it'll make things pretty difficult to read, and introduces some unnecessary syntax noise.
Use a plain for loop instead:
const filteredArr = {};
for (const item of LargeArr.Items) {
const { attributes: { dob, name, picture} = { dob: null, name: null, picture: null } } = item;
const pic = picture ? await getPic(picture) : null;
filteredArr[userID] = { name, uesrID, pic, dob };
}
If you really wanted to take the reduce route:
LargeArr.Items.reduce(
(acc, { attributes: { dob, name, picture} = { dob: null, name: null, picture: null }, userID }) => {
return acc.then(async (acc) => {
let pic = null;
if (picture) { pic = await getPic(picture); } // async here
acc[userID] = { name, userID, pic, dob };
return acc;
});
}, Promise.resolve({})
)
.then((filteredArr) => {
// do stuff with filteredArr
});
Unless the getPic calls need to be made in serial, you could consider using Promise.all instead, to iterate through the whole array at once, rather than waiting on the resolution of the prior Promise before going onto the next.
If your API can handle Promise.all:
const filteredArr = {};
await Promise.all(LargeArr.Items.map(async (item) => {
const { attributes: { dob, name, picture} = { dob: null, name: null, picture: null } } = item;
const pic = picture ? await getPic(picture) : null;
filteredArr[userID] = { name, uesrID, pic, dob };
}));

Dynamically creating a getter that returns a property of the object

I'm trying to create a getter within an object that returns the appropriate type of a field.
I got it working with this:
const getType = field => ({
email: "email",
telephone: "tel",
get type() {
return this[field] || "text";
}
});
My only issue here is that this is a function that returns an object.
I'm trying to take a slightly different approach, by creating a dynamic computed property based on the passed parameter.
I'd like to do something like this:
const getType = {
email: "email",
telephone: "tel",
get [type]() {
return this[type] || "text";
}
};
But this throws an error, Error: type is not defined.
Is what I am trying to do possible in JavaScript?
I'm aware I can just do something like,
const fieldsMatrix = {
email: 'email',
telephone: 'tel',
}
['email', 'username'].map(field => <input type={fieldsMatrix[prop] || 'text'} />)
But that's not what I'm asking in the question.
You can use a Proxy to intercept all attempts to get any property.
const getType = new Proxy({
email: "email",
telephone: "tel",
}, {
get(target, prop, receiver) {
return target[prop] || "text";
}
});
console.log(getType.email);
console.log(getType.somethingElse);
You can also define a function on the object instead.
const types = {
email: "email",
telephone: "tel",
getType(type) {
return this[type] || "text";
}
};
console.log(types.getType('email'));
console.log(types.getType('somethingElse'));
You can also do
const getType = field => {
const data = {
email: "email",
telephone: "tel",
};
const result = data[field] || "text";
Object.defineProperty(data,field,{
get:()=>result,
})
return data;
}

Why i cannot assign values to destructured object variables?

I have form in my website with onSubmit eventListener, so when user submits the form getCurrencyData function is executed. inside getCurrencyData function im checking whether the user entered value or not, if yes then im making apicall and destructuring generalCurrencyInfo object. The problem is that i cannot assign values to destructured object variables.
class App extends Component {
constructor(props) {
super(props);
this.state = {
generalCurrencyInfo: {
fullName: undefined,
name: undefined,
imageUrl: undefined,
price: undefined,
error: false
}
}
}
getCurrencyData = async (e) => {
e.preventDefault();
const CURRENCYNAME = e.target.elements.currencyName.value.toUpperCase();
//Checks if currency name is not empty
if (CURRENCYNAME) {
const APICALL = await fetch(`url`);
const DATA = await APICALL.json();
let generalCurrencyInfo = {
fullName:undefined,
name: undefined,
imageUrl: undefined,
price: undefined,
error: false
}
//this destructuring doesn't work
let {fullName, name, imageUrl, price, error} =generalCurrencyInfo;
if (DATA.Message === "Success") {
fullName = DATA.Data[0].CoinInfo.FullName;
name = DATA.Data[0].CoinInfo.Name;
imageUrl = `url`;
price = "price";
error = false;
}
this.setState({
generalCurrencyInfo: generalCurrencyInfo
})
}
}
render() {
return (
);
}
}
You have created 5 new variables here:
let {fullName, name, imageUrl, price, error} =generalCurrencyInfo;
Then you have changed this variables, but not generalCurrencyInfo object:
if (DATA.Message === "Success") {
fullName = DATA.Data[0].CoinInfo.FullName;
name = DATA.Data[0].CoinInfo.Name;
imageUrl = `url`;
price = "price";
error = false;
}
Here you set generalCurrencyInfo, what was not changed:
this.setState({
generalCurrencyInfo: generalCurrencyInfo
})
This will be fine:
this.setState({
fullName,
name,
imageUrl,
price,
error,
})
You can just reassign the values to your generalCurrencyInfo object, so no need to destructure:
// reassign values
if (DATA.Message === "Success") {
generalCurrencyInfo.fullName = DATA.Data[0].CoinInfo.FullName;
generalCurrencyInfo.name = DATA.Data[0].CoinInfo.Name;
generalCurrencyInfo.imageUrl = `url`;
generalCurrencyInfo.price = "price";
generalCurrencyInfo.error = false;
}
// or using the spread operator
if (DATA.Message === "Success") {
generalCurrencyInfo = {
...generalCurrencyInfo,
fullName: DATA.Data[0].CoinInfo.FullName,
name: DATA.Data[0].CoinInfo.Name,
imageUrl: `url`,
price: "price",
error: false,
};
}
But if you landed on this page looking to find out how to re-assign a value to a destructured object, you might want to check out this question: Is it possible to destructure onto an existing object? (Javascript ES6)

How to supply default values to ES6 class properties?

I have a JavaScript class I would like to supply with default values using an object. I only want the default values to be part of the class if user input is not otherwise supplied for some of the values. However, I am not sure how to implement this. Here is my class:
// Class definition, properties, and methods
class iTunesClient {
constructor(options) {
this.term = options.terms;
this.country = options.country;
this.media = options.media;
this.entity = options.entity;
this.attribute = options.attribute;
this.callback = options.callback;
this.limit = options.limit;
this.lang = options.lang;
this.version = options.version;
this.explicit = options.explicit;
this.url = options.url;
}
}
Here are my default values:
// Default values defined according to iTunes API
const defaults = {
terms: 'default',
country: 'US',
media: 'all',
entity: '',
attribute: '',
callback: '',
limit: 50,
lang: 'en-us',
version: 2,
explicit: 'yes',
url: '',
};
I realize this is possible through default parameters for functions, but I would rather supply an object containing the default values.
A typical way to do this is to use Object.assign() to merge passed-in values with default values:
// Class definition, properties, and methods
class iTunesClient {
constructor(options) {
// Default values defined according to iTunes API
const defaults = {
terms: 'default',
country: 'US',
media: 'all',
entity: '',
attribute: '',
callback: '',
limit: 50,
lang: 'en-us',
version: 2,
explicit: 'yes',
url: '',
};
let opts = Object.assign({}, defaults, options);
this.term = opts.terms;
this.country = opts.country;
this.media = opts.media;
this.entity = opts.entity;
this.attribute = opts.attribute;
this.callback = opts.callback;
this.limit = opts.limit;
this.lang = opts.lang;
this.version = opts.version;
this.explicit = opts.explicit;
this.url = opts.url;
}
}
To explain how Object.assign() works here:
It starts with {} as a target (an empty object)
Then it copies all the defaults into the empty object
Then it copies all the passed in properties into that same target
Then it returns the target object which you use for initializing all your instance data
Of course, if your instance property names are the same as the ones in your options object, you could do this in a more DRY fashion like this:
// Class definition, properties, and methods
class iTunesClient {
constructor(options) {
// Default values defined according to iTunes API
const defaults = {
terms: 'default',
country: 'US',
media: 'all',
entity: '',
attribute: '',
callback: '',
limit: 50,
lang: 'en-us',
version: 2,
explicit: 'yes',
url: '',
};
let opts = Object.assign({}, defaults, options);
// assign options to instance data (using only property names contained
// in defaults object to avoid copying properties we don't want)
Object.keys(defaults).forEach(prop => {
this[prop] = opts[prop];
});
}
}
You could do it like this:
class iTunesClient {
constructor(options) {
// Default values defined according to iTunes API
const defaults = {
terms: 'default',
country: 'US',
media: 'all',
entity: '',
attribute: '',
callback: '',
limit: 50,
lang: 'en-us',
version: 2,
explicit: 'yes',
url: '',
};
this.term = opts.terms || defaults.terms;
this.country = opts.country || defaults.country;
this.media = opts.media || defaults.media;
this.entity = opts.entity || defaults.entity;
this.attribute = opts.attribute || defaults.attribute;
this.callback = opts.callback || defaults.callback;
this.limit = opts.limit || defaults.limit;
this.lang = opts.lang || defaults.lang;
this.version = opts.version || defaults.version;
this.explicit = opts.explicit || defaults.explicit;
this.url = opts.url || defaults.url;
}
}
But you have to be wary of 'falsy' values, e.g. if opts.limit is passed in as 0 then this.limit will be set to defaults.limit value, even though opt was defined.
I think the better solutions for that, stackoverflow.com/a/48775304/10325885
class User {
constructor(options = {}) {
this.name = options.name || "Joe";
this.age = options.age || 47;
}
}
I think is much cleaner to read, if you just use ||.
For something a little more modern than the examples above you can use destructuring and default parameters.
class iTunesClient {
constructor({
term = '',
country = 'US',
media = 'all',
entity = '',
attribute = '',
callback = '',
limit = 50,
lang = 'en-us,
version = 2,
explicit = 'yes',
url = '',
}) {
this.term = terms;
this.country = country;
this.media = media;
this.entity = entity;
this.attribute = attribute;
this.callback = callback;
this.limit = limit;
this.lang = lang;
this.version = version;
this.explicit = explicit;
this.url = url;
}
}
That way any params that are not part of the constructor object will get set to the default. You can even pass an empty object and just get all defaults.

Cannot Populate Javascript Object Properties with Values Using Function

I have the following Javascript object defined:
var APIUserItem = function () {
var
id = '',
account_id = '',
client_id = '',
user_name = '',
salutation = '',
first_name = '',
middle_name = '',
last_name = '',
organization_name = '',
alternate_email = '',
time_zone_id = '',
utcoffset = '',
date_created = new Date(),
last_updated_date = new Date(),
is_active = null,
is_approved = null,
classes = [],
groups = [],
permissions = [],
properties = {},
version_stamp_hash_string = '',
getFromData = function (data) {
id = data.ID;
account_id = data.AccountID;
client_id = data.ClientID;
user_name = data.UserName;
salutation = data.Salutation;
first_name = data.FirstName;
middle_name = data.MiddleName;
last_name = data.LastName;
organization_name = data.OrganizationName;
alternate_email = data.AlternateEmail;
time_zone_id = data.TimeZoneID;
utcoffset = data.UTCOffset;
date_created = data.DateCreated;
last_updated_date = data.LastUpdatedDate;
is_active = data.IsActive;
is_approved = data.IsApproved;
properties = data.Properties;
version_stamp_hash_string = data.VersionStampHashString;
// list of pointers to classes
$.each(data.Classes, function (index, value) {
class_pointer = new APIPointerItem();
class_pointer.ID = value.ID;
class_pointer.PublicID = value.PublicID;
class_pointer.Name = value.Name;
class_pointer.RelativeURI = value.RelativeURI;
classes.push(class_pointer);
});
// list of pointers to Groups
$.each(data.Groups, function (index, value) {
group_pointer = new APIPointerItem();
group_pointer.ID = value.ID;
group_pointer.PublicID = value.PublicID;
group_pointer.Name = value.Name;
group_pointer.RelativeURI = value.RelativeURI;
groups.push(group_pointer);
});
// list of permissions
$.each(data.Permissions, function (index, value) {
permission_pointer = new APIPermissionList();
permission_pointer.ID = value.ID;
permission_pointer.Description = value.Description;
permission_pointer.Category = value.Category;
permission_pointer.Level = value.Level;
permission_pointer.ResourceType = value.ResourceType,
permission_pointer.Action = value.Action;
permissions.push(permission_pointer);
});
};
return {
ID: id,
AccountID: account_id,
ClientID: client_id,
UserName: user_name,
Salutation: salutation,
FirstName: first_name,
MiddleName: middle_name,
LastName: last_name,
OrganizationName: organization_name,
AlternateEmail: alternate_email,
TimeZoneID: time_zone_id,
UTCOffset: utcoffset,
DateCreated: date_created,
LastUpdatedDate: last_updated_date,
IsActive: is_active,
IsApproved: is_approved,
Classes: classes,
Groups: groups,
Permissions: permissions,
Properties: properties,
VersionStampHashString: version_stamp_hash_string,
GetFromData: getFromData
};
};
When I new up an APIUserItem by calling:
var user = new APIUserItem();
user.GetFromData(data);
then try to access the values in the new item like so:
document.write(user.ID);
all of the property values come back empty, except for the collections, which contain the expected data. For example, I can loop through the Groups array from outside the object and access the properties of each group to display values.
The data object I passed to the GetFromData function call contains all the data - it just seems like the assignments to the local variables are not working? I know I must have made some obvious mistake in my code. Can anyone help me find my problem?
Your assignments to the local variables are probably working, but they're just that: assignments to local variables. Your code does nothing to update the properties of the object. The fact that you create the object via that return statement that initializes properties from the local variables does not create a magic link between the local variables and the properties.
Your "getFromData" function should look something like this:
function getFromData( data ) {
this.ID = data.ID;
this.AccountID = data.AccountID;
// and so on
}
The collection properties ("classes", "groups", "permissions") worked because they're objects, and you update their contents. That part's OK. Really, you don't need those local variables at all; you can just initialize the object properties directly.

Categories

Resources