I need to implement small ODM like feature. I get plain javascript object from database, and I need to convert it into my model class instance. Let's assume model looks like:
class Model{
constructor(){
this.a = '777';
---- whole bunch of other things ---
}
print(){
console.log(this.a);
}
}
So I need convert var a = {b:999, c:666} to instance of model and being able to call a.print() after, and when a.print() executed 777 should be placed in console. How to do that?
There have a simple method. Just assign the object to instance(this)
class Model
{
constructor(obj){
Object.assign(this, obj)
}
print(){
console.log(this.a);
}
}
let obj = {a: 'a', b: 'b', c: 'c'}
let m = new Model(obj)
console.log(m)
m.print() // 'a'
If I understand the question correctly, you can export a factory function and make use of Object.assign to extend your base Model:
// Export the factory function for creating Model instances
export default const createModel = function createModel(a) {
const model = new Model();
return Object.assign(model, a);
};
// Define your base class
class Model {
constructor() {
this.a = 777;
}
print() {
console.log(this.a, this.b, this.c)
}
}
And call it like:
const myModel = createModel({ b: 999, c: 666 });
myModel.print();
Babel REPL Example
Or, of course, you could forego the factory and pass a in as a parameter (or rest parameters) to the constructor but it depends on your preferred coding style.
I would suggest rewriting your class to store all its properties in a single JS object this.props and accept this object in its constructor:
class Model {
constructor (props = this.initProps()) {
this.props = props
// other stuff
}
initProps () {
return {a: '777'}
}
print () {
console.log(this.props.a)
}
}
Then you'll be able to store this.props in your database as a plain JS object and then use it to easily recreate corresponding class instance:
new Model(propsFromDatabase)
Though, if you don't want to move all properties to this.props, you could use Object.assign to keep your object plain:
class Model {
constructor (props = this.initProps()) {
Object.assign(this, props)
// other stuff
}
initProps () {
return {a: '777'}
}
print () {
console.log(this.a)
}
}
But I would recommend using the former approach, because it'll keep you safe from name collisions.
If you need to typecast more consistently, you can also create your own typecast function like generic function
function typecast(Class, obj) {
let t = new Class()
return Object.assign(t,obj)
}
// arbitrary class
class Person {
constructor(name,age) {
this.name = name
this.age = age
}
print() {
console.log(this.name,this.age)
}
}
call it to typecast any object to any class instance like
let person = typecast(Person,{name:'Something',age:20})
person.print() // Something 20
How about this?:
var a = Object.create(Model.prototype, {
b: {
enumerable: true, // makes it visible for Object.keys()
writable: true, // makes the property writable
value: 999
}, c: {
value: 666
}
});
You'd be basically creating a new instance of Model from it's prototype and assigning your new properties to it. You should be able to call print as well.
You could have a static Model.from or Model.parse method, that returns a new Model with those properties:
class Model {
static defaults = { a: 777, b: 888, c: 999, d: 111, e: 222 };
constructor() {
const { defaults } = Model;
for (const key in defaults) this[key] = defaults[key];
}
print() {
console.log(this.a);
}
static from(data) {
const { defaults } = Model;
return Object.assign(
new Model(),
defaults,
Object.fromEntries(
Object.entries(data).filter(([key]) => key in defaults)
)
);
}
}
const data = {
a: "a", b: "b", c: "c", ajkls: "this wont be included"
};
const myModel = Model.from(data);
console.log("myModel =", myModel);
console.log("myModel instanceof Model:", myModel instanceof Model);
console.log("myModel.print():")
myModel.print();
Just like G_hi3's answer, but it "automates" the creation of the properties object
function Model() {
this.a = '777';
}
Model.prototype.print = function(){
console.log(this.a);
}
// Customize this if you don't want the default settings on the properties object.
function makePropertiesObj(obj) {
return Object.keys(obj).reduce(function(propertiesObj, currentKey){
propertiesObj[currentKey] = {value: obj[currentKey]};
return propertiesObj;
}, {}); // The object passed in is the propertiesObj in the callback
}
var data = {a: '888'};
var modelInstance = Object.create(Model.prototype, makePropertiesObj(data));
// If you have some non trivial initialization, you would need to call the constructor.
Model.call(modelInstance);
modelInstance.print(); // 888
First declare a class in which you want to convert JSON:
class LoginResponse {
constructor(obj) {
Object.assign(this, obj);
}
access_token;
token_type;
expires_in;
}
Now convert the general javascript object into your desired class object:
const obj = {
access_token: 'This is access token1',
token_type: 'Bearer1',
expires_in: 123,
};
let desiredObject = new LoginResponse(obj);
console.log(desiredObject);
Output will be:
LOG {"access_token": "This is access token1", "expires_in": 123, "token_type": "Bearer1"}
Related
I have a method which can return any type of object in my typescript project.
In java you would just return Object and then the caller has to cast.
How would you do that in Typescript ?
Do all classes inherit from a common super class like the Java Object type ??
If you need anything and you will know later what it is, use unknown (you will need to use as type later):
function test(): unknown {
return { a: 0, b: 1 };
}
test().a // Error
(test() as {a:number,b:number}).a // Ok
If you won't know and care what it is, use any (but this case typesafety goes out of the window):
function test(): any {
return { a: 0, b: 1 };
}
test().a // Ok
If you know at least it's object, you can type it as object and use Object functions or access it through type guards:
function test(): object {
return { a: 0, b: 1 };
}
Object.keys(test()) // Ok
// Or use with type guard:
const tst = test()
if ("a" in tst) {
tst.a // Ok
}
Also see Assignability documentation.
Most of the time the caller will know what to expect. What you can do is to infer the type when calling the function like so:
class A {
public returnAnyThing<T>(): T {
return {} as T;
}
}
interface ReturnValue {
val: number;
}
class Caller {
public call(){
const returnValue = new A().returnAnyThing<ReturnValue>();
console.log(returnValue.val);
}
}
Currently, I have a class of this sort:
class MyClass {
constructor(c) {
this.a = "a";
this.b = "b";
}
myMethod() {
return c;
}
}
As you can see c would be a property that I want to be returned when I execute a method on an instance, but it is not in the object instance.
Private properties would not work, because if I stringify the object, the property is also in the string, and I don't want it there.
Is there any way to achieve this? Not necessarily a complete solution but some hints would be enough.
Private properties would not work, because if I stringify the object, the property is also in the string
No it's not? This works fine:
class MyClass {
#c;
constructor(c) {
this.a="a";
this.b="b";
this.#c=c;
}
myMethod() {
return this.#c;
}
}
const obj = new MyClass('hi');
console.log(JSON.stringify(obj));
console.log(obj.myMethod());
An alternative would be to create the method in the constructor, as a closure over the c variable:
class MyClass {
constructor(c) {
this.a="a";
this.b="b";
this.myMethod = () => {
return c;
};
}
}
const obj = new MyClass('hi');
console.log(JSON.stringify(obj));
console.log(obj.myMethod());
Further alternatives that work with normal properties and prevent inclusion in the JSON.stringify result are to make the c property non-enumerable or to define a custom toJSON method.
In my real world class I added the properties:
class MyClass{
//other props
preQ: string;
postQ: string;
constructor(data: InputData = { cli: cli.flags }) {
Object.defineProperties(this, {
preQ: { enumerable: false },
postQ: { enumerable: false },
});
// other stuff
}
}
As indicated to in the first comment by Pointy. The properties are not present in the JSON.stringify result.
Which are properties that I do not want to be sent to the server.
Use static Keyword :
MDN Documentation :
Static properties cannot be directly accessed on instances of the
class. Instead, they're accessed on the class itself.
Static methods are often utility functions, such as functions to
create or clone objects, whereas static properties are useful for
caches, fixed-configuration, or any other data you don't need to be
replicated across instances.
Example
class Student {
name: string;
static age: number;
constructor(age: number) {
this.name = 'Jhon';
Student.age = age;
}
static getAge = () => Student.age;
}
const student = new Student(20);
const json = JSON.stringify(student); // {"name":"Jhon"}
console.log(json);
console.log(Student.getAge()); // 20
Your code:
class MyClass {
a: string;
b: string;
static c: string;
constructor(c:string) {
this.a = 'a';
this.b = 'b';
MyClass.c = c;
}
myMethod() {
return MyClass.c;
}
}
const obj = new MyClass('hi');
console.log(JSON.stringify(obj)); // {"a":"a","b":"b"}
console.log(obj.myMethod()); // hi
I need to build a structure (i need to do it for jestjs, in order to stub a module) that allows me to access a method in the following way:
// I make an instance of a class
const myTest = new Test()
// I access a method in this way
const result = myTest.prop1.prop2.prop3.getAll()
A basic class would look like
class Test2 {
constructor(){}
//getter
getAll(){
return 'Salutes'
}
}
And I can access getAll()
const myTest2 = new Test()
const result = myTest.getAll()
//in this way result contains a string (*salutes*)
So how can I add more properties in the middle? I found something, but in typescript... I need to do it in javascript
Assuming you need exactly the structure you've specified, in Test you'd have to create the object and have any methods you need be arrow functions or bound functions (if they need to access information from the Test instance; your example didn't so it wouldn't matter whether they had the right this). For instance:
class Test {
constructor() {
this.prop1 = {
prop2: {
prop3: {
getAll: () => {
return "Salutes";
},
getAll2() { // Also works
return "Salutes";
},
getAll3: function() { // Also works
return "Salutes";
}
}
}
}
}
}
const myTest = new Test();
const result = myTest.prop1.prop2.prop3.getAll();
console.log(result);
console.log(myTest.prop1.prop2.prop3.getAll2());
console.log(myTest.prop1.prop2.prop3.getAll3());
Example of using information on the Test instance (it's the same except for the constructor parameter and message property):
class Test {
constructor(message) {
this.message = message;
this.prop1 = {
prop2: {
prop3: {
getAll: () => {
// `this` refers to the `Test` instance because
// this is an arrow function
return this.message;
}
// `getAll2` and `getAll3` wouldn't work because
// they would get `this` based on how they're called
// (it would be the `this.prop1.prop2.prop3` object).
}
}
}
}
}
// I make an instance of a class
const myTest = new Test("Salutes encore!");
// I access a method in this way
const result = myTest.prop1.prop2.prop3.getAll();
console.log(result);
If you want to achieve it without modifying the Test2 class, you can use Proxy class and define custom handler for get method:
class Test2 {
constructor(){}
getAll(){
return 'Salutes'
}
}
let instance = new Test2();
const handler = {
get: function(target, prop, receiver) {
if (['prop1', 'prop2', 'prop3'].includes(prop)) {
return receiver;
}
return Reflect.get(...arguments);
}
};
const proxy = new Proxy(instance, handler);
console.log(proxy.prop1.prop2.prop3.getAll());
console.log(proxy.prop7);
console.log(proxy.getAll());
Here is a little background.
So I have my parent class. Constructor receives two objects: the first one is the class where the instance of the parent object was created (using others classes which are inheriting parent class).
The second object are just some parameters
parent.js
class ParentClass {
constructor(nativeObject, params) {
this.nativeObject = nativeObject;
this.param1 = params.param1;
this.param2 = params.param2;
}
// some logic here
}
module.exports = { ParentClass };
child.js
class ChildClass extends ParentClass {
// some logic here
}
module.exports = { ChildClass };
I'm using the child class in the other class methods for creating it. I can have multiple files like below:
usingclasses.js
let { ChildClass } = require('path/to/my/child/class');
let opts = {
param1: 'idkSomeString';
param2: 42;
}
class MyCoolClass {
createChild() {
return new ChildClass(this, opts);
}
}
module.exports = { MyCoolClass };
anotherclasses.js
let { ChildClass } = require('path/to/my/child/class');
let opts = {
param1: 'thatAstringToo';
param2: 123;
}
class AnotherCoolClass{
alsoCreatesChild() {
return new ChildClass(this, opts);
}
}
module.exports = { AnotherCoolClass};
I can create multiple instances of the ChildClass in a different methods within one class:
thirdexample.js
let { ChildClass } = require('path/to/my/child/class');
let opts1 = {
param1: 'aStringAgain';
param2: 27;
}
let opts2 = {
param1: 'ABC';
param2: 33;
}
class ClassAgain{
firstMethod() {
return new ChildClass(this, opts1);
}
secondMethod() {
return new ChildClass(this, opts2);
}
}
module.exports = { ClassAgain };
The reason why I'm passing .this because I'm working with it in ParentClass
e.g for logging console.log(`I know this was created in ${this.nativeObject .constructor.name}`);
What I'm looking for is the way to get rid of passing .this every time I'm creating a new instance of the ChildClass
Reason behind this is that in one file (class) I can create up to 10+ instances of the ChildClass.
In the previous question I already got an answer which helped me to make the code more structured and more easy to maintain:
Instead of:
createChild() {
return new ChildClass(this, param1, param2, param3, paramN);
}
I'm doing:
createChild() {
return new ChildClass(this, paramObj);
}
So now I can store all my objects with parameters at the top of the file (of even separate file) and easily find and change it if needed.
...
Now I'm trying to find a way to get rid of .this every time I'm creating ChildClass
Is there any other way how ParentClass can know where does this ChildClass instances "lives"?
I'm trying to figure out can I do something using .apply or .call but seems like this is not a droids I'm looking for
UPDATE:
Since I have zero progress on this and I even got a comment that most likely this is not possible let's assume for now that I need a name of the class that is creating instance of the ChildClass only for logging.
How can I retrieve the name of the class without passing it every time when creating a ChildClass?
let opts = {
className: 'MyCoolClass'
param1: 'idkSomeString';
param2: 42;
}
class MyCoolClass {
createChild() {
return new ChildClass(opts);
}
}
module.exports = { MyCoolClass };
The problem is that I will have to set className: 'MyCoolClass' in each version of opts again and again and again... Just repeat the same for 10-15 times
You can just instantiate your classes using Reflect.construct.
class ParentClass {
constructor (nat, x) {
this.nat = nat
this.x = x
}
log () {
console.log('from ', this.nat.constructor.name, this.x)
}
}
class ChildClass extends ParentClass{}
class ClassAgain{
firstMethod () {
return this.makeInstance({ a: 1 })
}
secondMethod () {
return this.makeInstance({ b: 2 })
}
makeInstance (params) {
return Reflect.construct(ChildClass, [this, params])
}
}
const ca = new ClassAgain()
ca.firstMethod().log()
ca.secondMethod().log()
Let's say I have an object:
const a = {
foo: 123,
bar: 'example'
}
This object is a part of many other objects i.e.
const b = {
a: a,
anotherField: "example"
}
Actually, I'm using TypeScript and all these objects are of the same class which I believe isn't important.
After serializing the b object to JSON I need to get this string (i.e. I just get the foo field from a):
{ a: 123, anotherField: "example" }
What is the easiest and most elegant way to tell JSON.stringify() how to convert the a object to a string?
Probably something similar to what Python allows.
You could define toJSON in a.
If an object being stringified has a property named toJSON whose value is a function, then the toJSON() method customizes JSON stringification behavior: instead of the object being serialized, the value returned by the toJSON() method when called will be serialized.
(source: MDN)
For example:
class A {
constructor(foo, bar) {
this.foo = foo;
this.bar = bar;
}
toJSON() {
return this.foo;
}
}
const a = new A(123, "some name");
const b = {
a: a,
anotherField: "example"
};
console.log(JSON.stringify(b)); // "{"a":123,"anotherField":"example"}"
You could use the replacer while stringifying:
const result = JSON.stringify(b, (k, v) => v && v.stringify() || v);
That way you can easily add a custom stringification to a:
const a = {
foo: 123,
bar: 'example',
stringify() { return this.foo; }
}