Get arguments of constructor in JS class - javascript

How can i get an array of the arguments of a constructor class in JS? It's possible? Thanks in advance.
class Product {
constructor(id, name, price, category, stock){
this.id = id;
this.name = name;
this.price = price;
this.category = category;
this.stock = stock;
}
};
console.log(Product.constructor.params);
//expected output = ['id', 'name', 'price', 'category', 'stock'];

Inspired form #Porter answer and #evolutionxbox, I think that a reliable way would be using .match() like this:
class Product {
constructor(id, name, price, category, stock, unusedArgument) {
this.id = id;
this.name = name;
this.price = price;
this.category = category;
this.stock = stock;
}
}
class Noarguments {
constructor() {
}
}
// A function
function getClassContructorParams(obj){
let match = obj.toString().match(/constructor\((.+)\)/)
if(match && match[1]){
return match[1].split(",");
}
// If no match
return []
}
console.log(getClassContructorParams(Product))
/// Testing a class with no constructor arguments will return an empty array
console.log(getClassContructorParams(Noarguments))
My previous answer was returning the object properties, which may be different from the arguments used in the constructor...
Now using .match() will ensure what is returned really are the constructor arguments.

let s = Product.toString();
let params = s.substring(s.indexOf('(')+1, s.indexOf(')')).split(',')

My point in the comments was it seems you're coming at this problem from the wrong direction. Maybe take this approach. Have an array of labels, and array of data, and pass those into the class. You'll still have the array of labels to access (and validate) in your other code, and everything will still work.
const labels = [ 'id', 'name', 'price', 'category', 'stock' ];
const data = [ 1, 'Bob', 100, 2, 1];
class Product {
constructor(labels, data) {
data.forEach((el, i) => this[labels[i]] = el);
}
};
console.log(new Product(labels, data));
console.log(labels);
Or, if your products are identical in terms of properties you could just use an array of them, and use Object.keys to get the labels of the first object.
const data = [{ id: 1, name: 'Bob', price: 100, category: 2, stock: 1 }];
const labels = Object.keys(data[0]);
class Product {
constructor(data) {
for (let key in data) {
this[key] = data[key];
}
}
};
console.log(new Product(data[0]));
console.log(labels);

Related

How can I define a static method in JavaScript to use with or without parenthesis?

I have a ES6 Vehicle class defined below. I want to have a static method such that I can use with or without parenthesis. I have defined static AvailableTypes and static AvailableTypes() but it gives an error TypeError: Vehicle.AvailableTypes is not a function
class Vehicle {
constructor({ vehicleType = 'car', name = '', range = '', seats = '' }) {
this.vehicleType = vehicleType
this.name = name
this.range = range
this.seats = seats
}
getRangeToSeatsRatio() {
return this.range / this.seats
}
get rangeToSeatsRatio() {
return this.range / this.seats
}
static AvailableTypes = ['car', 'plane']
static AvailableTypes() {
return this.AvailableTypes
}
}
const vehicle = new Vehicle({ name: 'My Car name', seats: 500, wheels: 45 })
console.log(Vehicle.AvailableTypes) // (2) ['car', 'plane']
console.log(Vehicle.AvailableTypes()) //TypeError: Vehicle.AvailableTypes is not a function
This should result in availableVehicleTypes being ['car', 'plane']
const availableVehicleTypes = Vehicle.AvailableTypes;
This should also result in availableVehicleTypes being ['car', 'plane']
const availableVehicleTypes = Vehicle.AvailableTypes();
It is impossible to create a value that is both a function and an array.
Make up your mind and choose only one of the two, it'll be much less confusing to the users of your class.

creating objects how can I get nested objects too

I am creating a constructor function. So I need to get my object everything is good except nested objects, how can i do that part?
function creation(name, value, id, name) {
this.name = name;
this.value = value;
this.category = {
id: "",
name: ""
}
}
let creationOne = new creation('book', 350, 1, "Something extraoridnary");
console.log(creationOne);
You need different names for the two name parameters. And then you need to use the parameter variables when creating the nested object.
function creation(object_name, value, id, category_name) {
this.name = object_name;
this.value = value;
this.category = {
id: id,
name: category_name
}
}
let creationOne = new creation('book', 350, 1, "Something extraoridnary");
console.log(creationOne);
Using ES6 Object Property Value Shorthand
Rename the first parameter to bookName.
Now you can assign values to the id and name property inside the category object.
So, you can either do: this.category = {id: id, name: name} OR simply this.category = {id, name}.
function creation(bookName, value, id, name) {
this.name = bookName;
this.value = value;
this.category = {id, name}
}
let creationOne = new creation('book', 350, 1, "Something extraoridnary");
console.log(creationOne);
One option is to pass in the category as an object:
function creation(name, value, category) {
this.name = name;
this.value = value;
this.category = category;
}
let creationOne = new creation('book', 350, { id: 1, name: "Something extraoridnary" });
console.log(creationOne);

Assign object values to class values

so I've encountered a problem with assigning object values to class values. Basically, let's say that I have a class Account and an object with the same properties as the class
class Account {
id: Number;
name: String;
}
const accountObject = {
id: 4216,
name: "Test name"
}
const account = new Account();
//set values from accountObject to account as a class
account.id = accountObject.id;
//...
So is there a way to assign values from an object to a class without doing it manually? I have a lot of properties that I need to be assigned and doing it by hand would solve the issue but if there's a prettier way to do so, I'd really appreciate any help
A simple loop should do the trick:
class Foo {
name = "foo"
age = 1
}
const foo = new Foo()
const bar = {
name: "bar",
age: 100
}
for (let key in bar) {
foo[key] = bar[key]
}
console.log(foo) // prints Foo { name: 'bar', age: 100 }
console.log('----------------------------------');
Object.entries(bar).forEach(
([key, value]) => (foo[key] = value)
)
console.log(foo) // prints Foo { name: 'bar', age: 100 }
class Account {
constructor({
id,
name
}) {
this.id = id;
this.name = name;
}
}
const account = new Account({
id: 4216,
name: "Test name"
});
console.log(account);

Assign dynamically nested array of classes

I need to be able to receive data from an external API and map it dynamically to classes. When the data is plain object, a simple Object.assign do the job, but when there's nested objects you need to call Object.assign to all nested objects.
The approach which I used was to create a recursive function, but I stumble in this case where there's a nested array of objects.
Classes
class Organization {
id = 'org1';
admin = new User();
users: User[] = [];
}
class User {
id = 'user1';
name = 'name';
account = new Account();
getFullName() {
return `${this.name} surname`;
}
}
class Account {
id = 'account1';
money = 10;
calculate() {
return 10 * 2;
}
}
Function to initialize a class
function create(instance: object, data: any) {
for (const [key, value] of Object.entries(instance)) {
if (Array.isArray(value)) {
for (const element of data[key]) {
// get the type of the element in array dynamically
const newElement = new User();
create(newElement, element)
value.push(newElement);
}
} else if (typeof value === 'object') {
create(value, data[key]);
}
Object.assign(value, data);
}
}
const orgWithError = Object.assign(new Organization(), { admin: { id: 'admin-external' }});
console.log(orgWithError.admin.getFullName()); // orgWithError.admin.getFullName is not a function
const org = new Organization();
const data = { id: 'org2', admin: { id: 'admin2' }, users: [ { id: 'user-inside' }]}
create(org, data);
// this case works because I manually initialize the user in the create function
// but I need this function to be generic to any class
console.log(org.users[0].getFullName()); // "name surname"
Initially I was trying to first scan the classes and map it and then do the assign, but the problem with the array of object would happen anyway I think.
As far as I understand from your code, what you basically want to do is, given an object, determine, what class it is supposed to represent: Organization, Account or User.
So you need a way to distinguish between different kinds of objects in some way. One option may be to add a type field to the API response, but this will only work if you have access to the API code, which you apparently don't. Another option would be to check if an object has some fields that are unique to the class it represents, like admin for Organization or account for User. But it seems like your API response doesn't always contain all the fields that the class does, so this might also not work.
So why do you need this distinction in the first place? It seems like the only kind of array that your API may send is array of users, so you could just stick to what you have now, anyway there are no other arrays that may show up.
Also a solution that I find more logical is not to depend on Object.assign to just assign all properties somehow by itself, but to do it manually, maybe create a factory function, like I did in the code below. That approach gives you more control, also you can perform some validation in these factory methods, in case you will need it
class Organization {
id = 'org1';
admin = new User();
users: User[] = [];
static fromApiResponse(data: any) {
const org = new Organization()
if(data.id) org.id = data.id
if(data.admin) org.admin = User.fromApiResponse(data.admin)
if(data.users) {
this.users = org.users.map(user => User.fromApiResponse(user))
}
return org
}
}
class User {
id = 'user1';
name = 'name';
account = new Account();
getFullName() {
return `${this.name} surname`;
}
static fromApiResponse(data: any) {
const user = new User()
if(data.id) user.id = data.id
if(data.name) user.name = data.name
if(data.account)
user.account = Account.fromApiResponse(data.account)
return user
}
}
class Account {
id = 'account1';
money = 10;
calculate() {
return 10 * 2;
}
static fromApiResponse(data: any) {
const acc = new Account()
if(data.id) acc.id = data.id
if(data.money) acc.money = data.money
return acc
}
}
const data = { id: 'org2', admin: { id: 'admin2' }, users: [ { id: 'user-inside' }]}
const organization = Organization.fromApiResponse(data)
I can't conceive of a way to do this generically without any configuration. But I can come up with a way to do this using a configuration object that looks like this:
{
org: { _ctor: Organization, admin: 'usr', users: '[usr]' },
usr: { _ctor: User, account: 'acct' },
acct: { _ctor: Account }
}
and a pointer to the root node, 'org'.
The keys of this object are simple handles for your type/subtypes. Each one is mapped to an object that has a _ctor property pointing to a constructor function, and a collection of other properties that are the names of members of your object and matching properties of your input. Those then are references to other handles. For an array, the handle is [surrounded by square brackets].
Here's an implementation of this idea:
const create = (root, config) => (data, {_ctor, ...keys} = config [root]) =>
Object.assign (new _ctor (), Object .fromEntries (Object .entries (data) .map (
([k, v]) =>
k in keys
? [k, /^\[.*\]$/ .test (keys [k])
? v .map (o => create (keys [k] .slice (1, -1), config) (o))
: create (keys [k], config) (v)
]
: [k, v]
)))
class Organization {
constructor () { this.id = 'org1'; this.admin = new User(); this.users = [] }
}
class User {
constructor () { this.id = 'user1'; this.name = 'name'; this.account = new Account() }
getFullName () { return `${this.name} surname`}
}
class Account {
constructor () { this.id = 'account1'; this.money = 10 }
calculate () { return 10 * 2 }
}
const createOrganization = create ('org', {
org: { _ctor: Organization, admin: 'usr', users: '[usr]' },
usr: { _ctor: User, account: 'acct' },
acct: { _ctor: Account }
})
const orgWithoutError = createOrganization ({ admin: { id: 'admin-external' }});
console .log (orgWithoutError .admin .getFullName ()) // has the right properties
const data = { id: 'org2', admin: { id: 'admin2' }, users: [ { id: 'user-inside' }]}
const org = createOrganization (data)
console .log (org .users [0] .getFullName ()) // has the right properties
console .log ([
org .constructor .name,
org .admin .constructor.name, // has the correct hierarchy
org .users [0]. account. constructor .name
] .join (', '))
console .log (org) // entire object is correct
.as-console-wrapper {min-height: 100% !important; top: 0}
The main function, create, receives the name of the root node and such a configuration object. It returns a function which takes a plain JS object and hydrates it into your Object structure. Note that it doesn't require you to pre-construct the objects as does your attempt. All the calling of constructors is done internally to the function.
I'm not much of a Typescript user, and I don't have a clue about how to type such a function, or whether TS is even capable of doing so. (I think there's a reasonable chance that it is not.)
There are many ways that this might be expanded, if needed. We might want to allow for property names that vary between your input structure and the object member name, or we might want to allow other collection types besides arrays. If so, we probably would need a somewhat more sophisticated configuration structure, perhaps something like this:
{
org: { _ctor: Organization, admin: {type: 'usr'}, users: {type: Array, itemType: 'usr'} },
usr: { _ctor: User, account: {type: 'acct', renameTo: 'clientAcct'} },
acct: { _ctor: Account }
}
But that's for another day.
It's not clear whether this approach even comes close to meeting your needs, but it was an interesting problem to consider.

Javascript using filter/includes on an array of objects

What is the best way to filter out data that exists within an object?
I was able to do use the below code when data was just an array of values but now I need to filter out any data where the item.QID exists in my array of objects.
Data Obj:
var data = [{
QID: 'ABC123',
Name: 'Joe'
},
{
QID: 'DEF456',
Name: 'Bob
}]
Snippet:
// I don't want to include data if this QID is in my object
this.employees = emp.filter(item =>!this.data.includes(item.QID));
From what I understand, includes only works on an array so I need to treat all of the QID values in my object as an array.
Desired Outcome: (assuming item.QID = ABC123)
this.employees = emp.filter(item =>!this.data.includes('ABC123'));
Result:
var data = [{
QID: 'DEF456',
Name: 'Bob'
}]
UPDATE:
Apologies, I left some things a little unclear trying to only include the necessary stuff.
// People Search
this.peopleSearchSub = this.typeahead
.distinctUntilChanged()
.debounceTime(200)
.switchMap(term => this._mapsService.loadEmployees(term))
.subscribe(emp => {
// Exclude all of the current owners
this.employees = emp.filter((item) => item.QID !== this.data.QID);
}, (err) => {
this.employees = [];
});
The above code is what I am working with. data is an object of users I want to exclude from my type-ahead results by filtering them out.
The question is a little ambiguous, but my understanding (correct me if I'm wrong), is that you want to remove all items from a list emp that have the same QID as any item in another list data?
If that's the case, try:
this.employees = emp.filter(item => !this.data.some(d => d.QID === item.QID))
some is an array method that returns true if it's callback is true for any of the arrays elements. So in this case, some(d => d.QID === item.QID) would be true if ANY of the elements of the list data have the same QID as item.
Try Object#hasOwnProperty()
this.employees = emp.filter(item =>item.hasOwnProperty('QID'));
You can use a for ... in to loop through and filter out what you want:
const data = [{
QID: 'ABC123',
Name: 'Joe'
},
{
QID: 'DEF456',
Name: 'Bob'
}]
let newData = [];
let filterValue = 'ABC123';
for (let value in data) {
if (data[value].QID !== filterValue) {
newData.push(data[value]);
}
}
newData will be your new filtered array in this case
You can use an es6 .filter for that. I also added a couple of elements showing the filtered list and an input to allow changing of the filtered value. This list will update on the click of the button.
const data = [{
QID: 'ABC123',
Name: 'Joe'
},
{
QID: 'DEF456',
Name: 'Bob'
}]
displayData(data);
function displayData(arr) {
let str = '';
document.getElementById('filterList').innerHTML = '';
arr.forEach((i) => { str += "<li>" + i.QID + ": " + i.Name + "</li>"})
document.getElementById('filterList').innerHTML = str;
}
function filterData() {
let filterValue = document.getElementById('filterInput').value;
filterText (filterValue);
}
function filterText (filterValue) {
let newArr = data.filter((n) => n.QID !== filterValue);
displayData(newArr)
}
<input id="filterInput" type="text" value="ABC123" />
<button type ="button" onclick="filterData()">Filter</button>
<hr/>
<ul id="filterList"><ul>

Categories