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
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);
}
}
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());
My problem:
I need to differentiate between the private, public and getter (get X()) properties of a typescript class.
My Project:
I have an Angular project, that has a model design pattern. Aka. an user model would look like this
class UserModel extends BaseModel {
private _id: number;
get id() { return this._id; }
set id( _id: number ) { this._id = _id; }
}
To send these models to the backend, I just JSON.stringify() them, which if the user id is set as 13, returns an object like this
{
_id: 13
}
Now I need to modify the toJSON() function on the UserModel, so that instead of returning the private properties of the object, I will return the get X() variables only. The output should look like this.
{
id: 13
}
I made this simple function, to retrieve all properties of an object, but this gives me the private properties and the get properties both.
abstract class BaseModel {
public propsToObj() : {[key: string]: any} {
let ret: any = {};
for (var prop in this) {
ret[prop] = this[prop];
}
return ret;
}
}
and the toJSON function looks like this
class UserModel extends BaseModel {
private _id: number;
get id() { return this._id; }
set id( _id: number ) { this._id = _id; }
toJSON() {
return this.propsToObj();
}
}
The outcome of stringify-ing the UserModel looks like this
{
_id: 13,
id: 13
}
In conclusion, I need to know the visibility and type (getter or regular variable?) of properties on a class, how would I achieve this?
your propsToObj() is working wrong, it gets just all properties, you need to change it so it will get only getters, for example you can use this
abstract class BaseModel {
public propsToObj() : {[key: string]: any} {
let ret: any = {};
for (const prop in this) {
const descriptor = Object.getOwnPropertyDescriptor(this.constructor.prototype, prop);
if (descriptor && typeof descriptor.get === 'function') {
ret[prop] = this[prop];
}
}
return ret;
}
}
Object.getOwnPropertyDescriptor will get descriptor of a property and from which you can check if there is get function in descriptor, if it is then your property is getter, if not it is regular property, you can read more about descriptors here MDN(descriptors)
The last question you asked
I need to know the visibility and type of properties on a class, how
would I achieve this?
As I know you can't get the visibility of a property, as for the type if you want to know data type of a property you can use typeof for it.
EXAMPLE in propsToObj() method:
public propsToObj() : {[key: string]: any} {
let ret: any = {};
for (const prop in this) {
const descriptor = Object.getOwnPropertyDescriptor(this.constructor.prototype, prop);
if (descriptor && typeof descriptor.get === 'function') {
ret[prop] = this[prop];
console.log(typeof ret[prop]); // just exaple how you can know type you can save it with property too if you need it
}
}
return ret;
}
This is a typescript class with an exposed but not editable attribute:
export class Category {
private _id: number;
constructor(id: number){
this._id = id;
}
public get id(){
return this._id;
}
}
I would like to map it from a JSON like this:
{ id: 1 }
There are some obvious problems here:
I know that "_id" can't be magically mapped to "id" so maybe I could implement a custom logic that renames all the attributes with a _ before the name
I would like to keep the constructor with the id param but maybe I'm not able to map an object who require arguments before instantiation
With an empty constructor, I tried Object.assign(new Category(), jsonObject), however, this does not work since Cannot set property id of #<Category> which has only a getter
What I want to avoid is to write custom mapping logic for every class like this with private attributes, I tried some other solutions and libraries but didn't work for my situation, they're all referencing to class with only public attributes
I don't even know if what I ask is achievable, so if the case it isn't, then I will "surrender" to use the class with only public attributes
The missconception here is that you need a getter/setter at all just to manage visibility. You can't prevent code from accessing and modifying id, no matter what you do. You can however tell the IDE (and the developer using it) that he can only read/get the property by using the readonly modifier, which simplifies your code to:
export class Category {
readonly id: number;
}
Noe that readonly thing only exists at compile time, and doesnt have any affects at runtime. Your IDE will prevent you from doing:
(new Category).id = 5;
But it allows you to easily do:
const category = Object.assign(new Category, { id: 5 });
Pass the object through constructor:
export class Category {
private _a: number;
private _b: string;
constructor(values: { a: number; b: string }){
this._a = values.a;
this._b = values.b;
}
public getA() { return this._a; }
public getB() { return this._b; }
}
You can still call it with or without JSON:
let cat1 = Category (values: { a: 42, b: 'hello' });
let json = '{"a":42,"b":"hello"}'
let cat2 = Category (values: JSON.parse (json));
Alternatively, keep the values in an object rather than a direct member. This makes it unnecessary to map the object to member variables:
export class Category {
private _values: {
a: number;
b: string;
}
constructor(values: { a: number; b: string }){
this._values = values;
}
public getA() { return this._values.a; }
public getB() { return this._values.b; }
}
If you want to keep the old constructor, you can do so like this:
export class Category {
private _values: {
a: number;
b: string;
}
constructor(a: number, b: string) {
this._values = { a: a, b: b };
}
public static make (values: { a: number; b: string }) {
this._values = values;
}
public getA() { return this._values.a; }
public getB() { return this._values.b; }
}
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"}