How to supply default values to ES6 class properties? - javascript

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.

Related

Recursive function in javascript that outputs JSON

In plain javascript, I am trying to create a function that will return a tree structure (json) of a folder, its subfolders and any files. I'm trying to achieve this using recursion.
The problem with the below code is that it stops after the first recursive call.
I know that in JS you do references, and I need to create a new object that I pass the values from the previous call to, but I am struggling to do so.
function fun(file, json) {
var tempJson = {
'name' : json.name || '',
'children' : obj.children || new Object()
};
if (file.type == 'file') {
tempJson.type = 'file';
tempJson.children = {}; // this will be empty, since there are no children
}
else {
tempJson.type = 'dir';
var listed = file.listFiles();
if (listed.length > 0) {
for each (var item in listed) {
tempJson.children = fun(item, tempJson);
}
} else {
tempJson.children = {};
}
}
return tempJson;
}
Example
From a directory structure like:
-root
--file1
--dir1
---file1.1
--dir2
I would like to get a json like:
{
name: 'root',
type: 'dir',
children : [
{
name: 'file1',
type: 'file',
children: {}
},
{
name: 'dir1',
type: 'dir',
children:
{
name: 'file1.1',
type: 'file',
children: {},
}
},
name: 'dir2',
type: 'dir',
children: {}
}
First call:
var object = new Object();
fun(rootdir, object);
Hope this makes sense.
Thanks!
As pointed out in the comments, children should be an array:
function fun(entry) {
var entryObj = { // construct the object for this entry
name: entry.name || "",
type: entry.type, // put the type here instead of using an if
children: [] // children must be an array
};
if(entry.type === "dir") { // if this entry is a directory
var childEntries = entry.listFiles(); // get its child entries
for(var childEntry of childEntries) { // and for each one of them
entryObj.children.push(fun(childEntry)); // add the result of the call of 'fun' on them to the children array
}
}
return entryObj;
}
Then call it like so:
var tree = fun(rootEntry);

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

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))

Return object with subset of its attributes

I've got a flat JavaScript object like this:
{
id: 3726492,
kind: 'user',
permalink: 'nicholas',
username: 'Nicholas',
...
a lot more attributes
}
I'd like to create a new object which only has a subset of the attributes of the original object.
Something like
var newObject = oldObject.fields(['id', 'username']);
newObject would be
{
id: 3726492,
username: 'Nicholas'
}
Is there already something like this?
Try this
function pick(data, keys) {
var result = {};
keys.forEach(function (key) {
if (data.hasOwnProperty(key)) {
result[key] = data[key];
}
});
return result;
}
var data = {
id: 3726492,
kind: 'user',
permalink: 'nicholas',
username: 'Nicholas'
}
var newData = pick(data, ['id', 'kind']);
console.log(newData);
In underscorejs or lodash there is method .pick
var data = {
id: 3726492,
kind: 'user',
permalink: 'nicholas',
username: 'Nicholas',
};
var newObject = _.pick(data, 'id', 'username');
console.log(newObject);
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore.js"></script>
You can use Array.prototype.reduce to reduce one object to another using the list of properties:
function subset(obj, propList) {
return propList.reduce(function(newObj, prop) {
obj.hasOwnProperty(prop) && (newObj[prop] = obj[prop]);
return newObj;
}, {});
}
var obj = {
id: 3726492,
kind: 'user',
permalink: 'nicholas',
username: 'Nicholas'
};
var newObj = subset(obj, ['id', 'username']);
console.log(newObj);
document.getElementById('json').innerText = JSON.stringify(newObj);
<pre id="json"></pre>
Not built-in, but you can sure define a simple function that does the job:
var original = {a:1112, b:434, c:666, d:222};
function fieldSubset(obj, fields) {
var subsetClone = {};
for( var i=0,l=fields.length; i<l; i++) {
// This can prevent filling undefined as properties
if(obj.hasOwnProperty(fields[i])) {
subsetClone[fields[i]] = obj[fields[i]];
}
}
return subsetClone;
}
fieldSubset(original, ["a", "c"]);
You can also use this in Object.prototype, but be aware that this might happen to conflict with native API in the future versions of JavaScript:
var original = {a:1112, b:434, c:666, d:222};
Object.defineProperty(Object.prototype, "fieldSubset", {
value: function(fields) {
var subsetClone = {};
for( var i=0,l=fields.length; i<l; i++) {
// This can prevent filling undefined as properties
if(this.hasOwnProperty(fields[i])) {
subsetClone[fields[i]] = this[fields[i]];
}
}
return subsetClone;
},
enumerable: false,
configurable: true}
);
original.fieldSubset(["a", "c"]);
One liner using Array.prototype.reduce. We are also using Object.assign. The idea is to keep extending a blank object with the keys found in the filters array. If you see, the reduce function takes a callback function with arg1,arg2,arg3 params as the first argument and an empty object as the second argument. This object will be cloned and extended with the help of the keys specified in the filters array.
var a = {
id: 3726492,
kind: 'user',
permalink: 'nicholas',
username: 'Nicholas',
};
var filters = ["id","username","permalink"];
var sub = Object.keys(a).reduce((arg1,arg2,arg3)=>{ var res = {}; if(filters.indexOf(arg2)>=0){ res[arg2] = a[arg2]; } return Object.assign(arg1,res);},{})
console.log(sub);
You haven't specifically mentioned what is the type of values behind your object's keys. Your current answers cover the shallow copy and deep copy.
Another alternative would be to create a view of the original object. This would be helpful if you have very large data objects and you do not want them copy in the memory.
function View(obj, properties) {
var view = {};
properties.forEach(function(prop) {
Object.defineProperty(view, prop, {
get: function() {
return obj[prop];
},
set: function(val) {
obj[prop] = val;
},
enumerable: true,
configurable: true
});
});
return view;
}
then with your data you can do:
var data = {
id: 3726492,
kind: 'user',
permalink: 'nicholas',
username: 'Nicholas',
},
view = new View(data, ['id', 'username']);
view.id; // 3736492
view.username; // Nicholas
of course you have to be aware that you can change your original object just by view.id = 'something else'. However it is easily preventable.

How can I get console.log to output the getter result instead of the string "[Getter/Setter]"?

In this code:
function Cls() {
this._id = 0;
Object.defineProperty(this, 'id', {
get: function() {
return this._id;
},
set: function(id) {
this._id = id;
},
enumerable: true
});
};
var obj = new Cls();
obj.id = 123;
console.log(obj);
console.log(obj.id);
I would like to get { _id: 123, id: 123 }
but instead I get { _id: 123, id: [Getter/Setter] }
Is there a way to have the getter value be used by the console.log function?
You can use console.log(Object.assign({}, obj));
Use console.log(JSON.stringify(obj));
Since Nodejs v11.5.0 you can set getters: true in the util.inspect options. See here for docs.
getters <boolean> | <string> If set to true, getters are inspected. If set to 'get', only getters without a corresponding setter are inspected. If set to 'set', only getters with a corresponding setter are inspected. This might cause side effects depending on the getter function. Default: false.
You can define an inspect method on your object, and export the properties you are interested in. See docs here: https://nodejs.org/api/util.html#util_custom_inspection_functions_on_objects
I guess it would look something like:
function Cls() {
this._id = 0;
Object.defineProperty(this, 'id', {
get: function() {
return this._id;
},
set: function(id) {
this._id = id;
},
enumerable: true
});
};
Cls.prototype.inspect = function(depth, options) {
return `{ 'id': ${this._id} }`
}
var obj = new Cls();
obj.id = 123;
console.log(obj);
console.log(obj.id);
I needed a pretty printed object without the getters and setters yet plain JSON produced garbage. For me as the JSON string was just too long after feeding JSON.stringify() a particularly big and nested object. I wanted it to look like and behave like a plain stringified object in the console. So I just parsed it again:
JSON.parse(JSON.stringify(largeObject))
There. If you have a simpler method, let me know.
On Node.js, I suggest using util.inspect.custom, which will allow you to pretty print getters as values, while keeping other properties output unchanged.
It will apply to your specific object only and won't mess the general console.log output.
The main benefit vs Object.assign is that it happens on your object, so you keep the regular generic console.log(object) syntax. You don't have to wrap it with console.log(Object.assign({}, object)).
Add the following method to your object:
[util.inspect.custom](depth, options) {
const getters = Object.keys(this);
/*
for getters set on prototype, use instead:
const prototype = Object.getPrototypeOf(this);
const getters = Object.keys(prototype);
*/
const properties = getters.map((getter) => [getter, this[getter]]);
const defined = properties.filter(([, value]) => value !== undefined);
const plain = Object.fromEntries(defined);
const object = Object.create(this, Object.getOwnPropertyDescriptors(plain));
// disable custom after the object has been processed once to avoid infinite looping
Object.defineProperty(object, util.inspect.custom, {});
return util.inspect(object, {
...options,
depth: options.depth === null ? null : options.depth - 1,
});
}
Here is a working example in your context:
const util = require('util');
function Cls() {
this._id = 0;
Object.defineProperty(this, 'id', {
get: function() {
return this._id;
},
set: function(id) {
this._id = id;
},
enumerable: true
});
this[util.inspect.custom] = function(depth, options) {
const getters = Object.keys(this);
/*
for getters set on prototype, use instead:
const prototype = Object.getPrototypeOf(this);
const getters = Object.keys(prototype);
*/
const properties = getters.map((getter) => [getter, this[getter]]);
const defined = properties.filter(([, value]) => value !== undefined);
const plain = Object.fromEntries(defined);
const object = Object.create(this, Object.getOwnPropertyDescriptors(plain));
// disable custom after the object has been processed once to avoid infinite looping
Object.defineProperty(object, util.inspect.custom, {});
return util.inspect(object, {
...options,
depth: options.depth === null ? null : options.depth - 1,
});
}
};
var obj = new Cls();
obj.id = 123;
console.log(obj);
console.log(obj.id);
Output:
Cls { _id: 123, id: 123 }
123
Use spread operator:
console.log({ ... obj });

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