convert an object to a class instance - javascript

I have a javascript app that reads a json file on the server; the function that reads the json file returns an object.
Until now I was using that object (it represents an album of photos) in the app, but now I want to make it an instance of a class, so that I'm going to manipulate it through the class methods.
Surely I'm doing something like:
var album = new Album(object);
My question is: what is the best way to generate the class properties in the constructor? can I iterate on the object properties with something like:
Object.keys(object).forEach(function(key) {
this[key] = object.key;
}
or should I assign manually each object property to the corresponding class property?

In this case, I'm going to say best is a matter of opinion. What you are doing will work and is clean and simple but it should fail due to this and the function reference also due to the missing ).
The following version should work for you.
Object.keys(object).forEach((key) => {
this[key] = object[key];
})

Related

JSON object with nested objects, all needing to be casted to classes in Typescript/Angular 5

Have done quite a bit of research on this but my situation seems to be a tad more complex than other examples i've seen on stack. I hope my explanation is clear enough to follow!
The API response I am working with is structured as an array of Customers. Each Customer has methods of its own as well as multiple nested objects. For the purposes of this question I'll focus on the Addons nested object (so Customer.Addons). Addons has some methods as well as an array of objects (addons). Each array item is an Addon object. So...I need to cast each level as the prototype of the associated Class.
This seems to work fine for Customer:
Object.setPrototypeOf(customer, Customer.prototype)
I am stuck on Addons even though I am using the same approach:
Object.setPrototypeOf(customer.addOns, Addons.prototype)
And for the array of addons (type Addon) I am iterating through like so. THIS seems to be working fine as well:
_.each(customer.addOns, (addon: Addon) => {
Object.setPrototypeOf(addon, Addon.prototype)
})
When I console.log this stuff, I can see that the Customer object is getting recognized as Customer. I can see that array items are correctly getting recognized as type Addon, but the Addons object, that middle layer, seems to be a plain object.
This image captures what I mean. Parent object of type Customer, child named addons with an array of items all of type Addon:
Here is my Addons class. It extends a generic class whose sole job is to have an array with some basic CRUD methods. The getSubtotal method is just returning a hard coded number for now (for testing) but will ultimately iterate through the associated array in its super and add numbers.
export class Addons extends PluralOfType<Addon> {
constructor() {}
...
getSubTotal(): number {
// eventually will return _.reduce(super.arr, ...) etc
return 10
}
}
Here is a segment from the PluralOfType generic:
class PluralOfType<T> {
public arr: T[] = [];
getAll(): T[] { return this.arr };
...
}
In my other typescript files, if I do this:
console.log(customer.addOns)
I will get the array of addons, each of type Addon. If I do this:
console.log(customer.addOns.getSubtotal())
I will get 10, which is correct. If I do this, which is a method on addOns superclass:
console.log(customer.addOns.getAll())
I get undefined. Moreover, because arr is public in the superclass, unsure why I can't just call customer.addOns.arr.
Clearly I am missing something here. Hopefully it's a simple fix! Appreciate any help.
Given some class object, class A {m(){}}, setting the prototype of an object, say o, to A.prototype does not construct A and then merge the resulting value's members into o. It just changes o's prototype. In other words it sets up delegation of member lookup such that, if o does not have a requested member, say m, then A.prototype will be considered and A.prototype.m will be reolved.
So, for
class PluralOfType {
arr = [];
getAll() {return this.arr;}
}
and
class AddOns extends PluralOfType {
getSubTotal() {return 10;}
}
if we write
const o = {};
Object.setPrototypeOf(o, AddOns.prototype);
console.log(o.getAll());
will print undefined because this is o in the expression this.arr.
We would then likely be tempted to write
const addOnsArray = customer.addOns.slice();
Object.setPrototypeOf(customer.addOns, AddOns.prototype);
Object.assign(customer.addOns, new AddOns());
But that will make arr an empty array which isn't what we actually want.
An unsound and dirty fix for the specific problem is to write
const addOnsArray = customer.addOns.slice();
Object.setPrototypeOf(customer.addOns, AddOns.prototype);
customer.addOns.arr = addOnsArray;
This in not general enough to be useful.
The entire approach dramatically increases the odds of introducing very serious and difficult to find bugs. It makes the system confusing.
I'm not just talking about arrays, I mean all of it.
You should scrap your entire approach. Don't change the prototypes of any values returned by JSON.parse at all.
You probably do not realize how hard you are making your task nor how much the people who inherit your project will hate you.
My recommendation is that you not use classes to define the functionality. If you really want to have methods in your objects add them to the objects directly. However, it would be simpler to keep the functionality outside the objects either as free standing functions or as methods in services. These would receive the object as an argument. Sometimes it does make sense to have functions and data together but this does not work well with serialization.

Get constructor name of object

How can I get name of object's class? I mean "Process" in this example
I see two ways to get it. First one is to write a getter in this class like
getClassName(){return "Process"}
But I suppose it will be an error if I try to call this method in object which doesn't belong to this class and hasn't got method like this.
And second one is using object instanceof Process. But maybe there is some way to make it better and more correctly?
You can get it from name on constructor:
console.log(object.constructor.name);
When you do ex = new Example, for instance, in the normal course of things that makes Example.prototype the prototype of the object that was created (ex), and the object inherits a constructor property from that object that refers back to the constructor (Example).
I say "in the normal course of things" because there are various ways those normal relationships can be changed. For instance, code could have overridden the constructor property with an own property on the object (ex.constructor = somethingElse;). To rule out that specific scenario, you could use:
console.log(Object.getPrototypeOf(object).constructor.name);
Live example:
class Example1 {
}
const e1 = new Example1();
console.log(e1.constructor.name); // "Example1"
class Example2 {
constructor() {
this.constructor = "I'm special";
}
}
const e2 = new Example2();
console.log(Object.getPrototypeOf(e2).constructor.name); // "Example2"
The TC39 committee members that specify JavaScript were happy enough to use the instance's constructor property in Promises when building the new promise that then and catch return (see StepĀ 3 here which goes here and reads constructor from the instance) (and in some other places), so you wouldn't be out on your own if you also used it. They don't even go to the prototype of the instance.
But yes, just for completeness, even if you go to the prototype for it, it's still possible for that to lead you astray, since the prototype's constructor property can also be mucked with:
class Example {
}
Example.prototype.constructor = Object; // Why would anyone do this? People are weird.
const e = new Example();
console.log(Object.getPrototypeOf(e).constructor.name); // "Object"
It's also possible to redefine the name on a function:
class Example {
}
// Why would someone do this? People are weird.
Object.defineProperty(Example, "name", {
value: "flibberdeegibbit"
});
const e = new Example();
console.log(Object.getPrototypeOf(e).constructor.name); // "flibberdeegibbit"
So...caveat user.
Note that the function name property is new as of ES2015 (as is class syntax). If you're using class syntax via a transpiler, it may or may not set name correctly.
Generally object instanceof Process is desirable if it's known for sure that object originates from this class/function. There may be situations where this won't be so. The appearance of several Process can be caused by iframes, multiple package versions, etc.
There is name property that already exists in regular functions class constructors. A known pitfall is that it will be mangled in minified code, so it is generally useless in browser JS, and its use can be considered an antipattern. name cannot be reassigned (in some browsers), so a separate property is needed to identify the class.
The proper way is to avoid this problem
But I suppose it will be an error if I try to call this method in object which doesn't belong to this class and hasn't got method like this.
is to use a getter:
class Process {
get className() { return 'Process'; }
...
}
Or a property:
class Process {
...
}
Process.prototype.className = 'Process';
As a result, there may be several Process classes that have Process className identifier. This may be desirable or not. While instanceof associates class instance with one particular class.
Use .constructor.name on the object. Each object's constructor by default refers to his creation function, which has a name property. It returns the name of the function.
class SomeClass {
}
const obj = new SomeClass();
console.log(obj.constructor.name);
Use the name property as follows:
class Process {}
console.log(Process.name);
const process = new Process;
console.log(process.constructor.name);
This is the same way it works for normal prototypal inheritance using functions.

How to communicate different levels in the same javascript object

I am trying to namespace my jQuery app.
Example:
var app = {
data : {
data1: ''
},
fn: {
setData1: function(value){
app.data.data1 = value
}
}
}
This works, but is there a better solution which doesn't use the full path? Moreover, is this a good code structure for a medium-sized application?
Javascript does not have syntax for specifying shortcuts to a parent element in a nested literal object definition. And, in fact, Javascript a nested object inside another object could have been defined anywhere and the reference assigned into the object and could be assigned into many objects so there is no real such thing as a parent at runtime as far as Javascript is concerned. Javascript just doesn't work that way.
As such, if you want to reference another element of the object that is not below it, then you will have to use the full path of object names starting from the top of the object.
As to whether this is a good way to do this, that is entirely context-specific. At first glance (and with no other context supplied), I don't see why you need the fn or data levels in your object. Data properties and methods can be at the same level and that is commonly done without adding extra levels. You will find that extra levels that aren't necessary just increase the amount of typing in your code and do not improve performance. You should give your methods and data properties meaningful enough names that it's very clear what they are and what they are used for. A name like data1 is not a particularly good example.
In addition, there is generally not a reason to make a setData1() method for a public data1 property, but if you put them at the same level, you can do use the this pointer in the method which will be set to app if someone calls app.setData1():
var app = {
data1: ''
setData1: function(value){
this.data1 = value;
}
}

Creating a new object by giving a handle to its definition

In this Udacity video on game development, the instructor mentions that Javascript allows us to create an object by giving a handle to its definition. Then, it says that to allow this "an overloaded object definition will update a hash table with a pointer to its class definition".
I quite know what a hash table, a pointer, an overloaded method, and the factory pattern are, but I cannot make sense of this mysterious statement, or of the rest of the explanation.
"Hash table" is just a fancier way of saying "ordinary Javascript object". What the instructor means by "handle to its definition" is just another way of saying "the function that acts as a constructor for the class".
Ultimately, what he means by the statement you mentioned:
each overloaded entity definition will update a hash table with a pointer to its class definition
is the following:
All "subclasses" of Entity will register their constructor function in a single shared hashmap/object using the key which is the type value.
This allows you to get the constructor function (in other words, the function to call new on, which will return an instance of that entity) for any type by accessing gGameEngine.factory[type].
This is nice, because whenever a programmer adds a new type of entity, so long as they remember to add a new entry to that gGameEngine.factory object with the correct key, then that object will contain everything you need to construct any type of supported object.
So the code that iterates over the JSON structure generated by the level editor can create an instance of any type the same way, using something like:
var typeConstructor = gGameEngine.factory(tileSpec.type),
instance;
if (typeConstructor) {
instance = new(typeConstructor)(tileSpec /* or whatever params */);
}
This is similar to the code visible at around the 1 minute mark of the video you linked to.
Make sense now?
I think all he's saying is that you can map references to functions / objects / variables inside another object. He's using one type of property accessor syntax, but I think he's overcomplicating things by using language like 'handle' and 'hash table'.
var someClass = function () {
// stuff
}
var containingObject = {};
containingObject["someClass"] = someClass;
// same thing as
containingObject.someClass = someClass;
Then you can instantiate the class by calling the containingObject property.
var classInstance = new containingObject["someClass"]()
// or
var classInstance = new containingObject.someClass()

Javascript class for enforcing JSON structure

Relatively new to JavaScript, so wondering what is the best approach to achive the below requirement.
I wanted to create JSON object (RPC request object), and need to enforce some structure. As I learn, JS is dynamic language hence I can add any type to an object at any time. But, I like to define a class, which I can populate to construct the JSON request.
var RecReq = {
ID:{},
HeaderLevel:{},
Content:{} //Content object goes here
},
HeaderLevel = {
HeaderID:{},
Note:{}
},
Content = {
Item: [] //Array of Item goes here
},
Item = {
IDs:[]
};
function ReceiveObj() {
this.RecReq = RecReq,
this.HeaderLevel = HeaderLevel,
this.Content = Content,
this.Item = Item
};
return new ReceiveObj();
I am sure many things wrong with the above code. I don't think the object is created with the arrays initialized.
On the returned object, I cannot do push() operation to insert an iten onto Content.
How would you approach this. Do you create an object on the fly, or better to define an object which enforces some structure.
If, as your code suggests, you want your instances to inherit the outer object, one approach would be to assign the prototype of the 'class' (be careful of thinking in class terms in JS - it's not a class in the Java etc sense) to the object.
var obj = {
prop_1: 'foo',
prop_1: 'bar'
}
function Something() {
alert(this.prop_1); //alerts "foo"
}
Something.prototype = obj;
var something = new Something();
This is just one way. There are many patterns for controlling inheritance (and other patterns that would achieve what you want without even going near the concept of inheritance).
As for push() not working, in your code, Content is an object, whereas push() is a method of the Array prototype. In other words, you can push() into an array only. Perhaps you meant Content.Item, which IS an array.
Lastly, I would avoid capitalising names. In JavaScript, this tends to be done only with functions that are used in class simulation (like your ReceiveObj), to denote that they should be instantiated, not merely invoked.

Categories

Resources