I have following Nestjs DTO class:
// create-job-offer.dto.ts
import { IsOptional, IsNumber } from 'class-validator';
export class CreateJobOfferDto {
#IsNumber()
#IsOptional()
mentorId: number;
#IsNumber()
#IsOptional()
companyId: number;
}
I want to obtain the list of class properties: ['mentorId', 'companyId'].
I tried so far in a controller without success following methods:
Object.getOwnPropertyNames(new CreateJobOfferDto());
Object.getOwnPropertyNames(CreateJobOfferDto);
Object.getOwnPropertySymbols(new CreateJobOfferDto());
Object.getOwnPropertySymbols(CreateJobOfferDto);
Object.getOwnPropertyDescriptors(CreateJobOfferDto);
Object.getOwnPropertyDescriptors(new CreateJobOfferDto());
Object.getPrototypeOf(CreateJobOfferDto);
Object.getPrototypeOf(new CreateJobOfferDto());
If I add a method, or vars in a constructor, I can get them, but not the properties.
The reason why I want to achieve this is, I am using Prisma and React, and in my React app I want to receive the list of class properties so that I can generate a model form dynamically.
There is no easy direct way to get the list of properties of a DTO class, or
more comprehensively, any type of class that has only properties.
The reason is that whenever you defined a property without any values, it becomes disappears after compiling to javascript.
Example:
// typescript
class A {
private readonly property1: string;
public readonly property2: boolean;
}
it compiles to this:
// javascript
"use strict";
class A {}
In order to achieve this goal, you need to write a custom decorator. like this:
const properties = Symbol('properties');
// This decorator will be called for each property, and it stores the property name in an object.
export const Property = () => {
return (obj: any, propertyName: string) => {
(obj[properties] || (obj[properties] = [])).push(propertyName);
};
};
// This is a function to retrieve the list of properties for a class
export function getProperties(obj: any): [] {
return obj.prototype[properties];
}
Imagine I have a DTO class for a user, like this:
import { getProperties } from './decorators/property.decorator';
export class UserDto {
#Property()
#IsNotEmpty()
firstName: string;
#Property()
#IsEmail()
#IsOptional()
public readonly email: string;
}
and I want to get all properties in a list, so we need to call the getProperties method which we defined earlier, so:
import { UserDto } from './dtos/user.dto';
getProperties(UserDto); // [ 'firstName', 'email' ]
You can also use an npm module ts-transformer-keys.
A TypeScript custom transformer which enables to obtain keys of given type.
How to use:
import { keys } from 'ts-transformer-keys';
interface Props {
id: string;
name: string;
age: number;
}
const keysOfProps = keys<Props>();
console.log(keysOfProps); // ['id', 'name', 'age']
Related
I have a class which is actually a Service in NestJS, so the problem I faced is that I cannot type it properly. I mean of course we can do an interface and say that class implements it, but what if I need to use it in generic. Let's take a look what I mean.
For example I want to do a Provider that can choose right class according to some data for example .env, for this purpose we should write a generic
type ServiceProviderType<T> = Record<string, T>;
Now the implementation of such a Provider
const Provider: ServiceProviderType<Interface> = {
default: DefaultService //--------------Problem occurs here
custom: CustomService
}
Error says that typeof DefaultService cannot be assigned to interface cause it doesn't have required methods, even though it has.
For Example this is Interface
export interface ServiceInterface {
getResult(expression: string): string;
exponentialToDecimal(exponential: string): string;
}
and this is Class
#Injectable()
export class DefaultService implements Interface {
constructor(
#Inject(EXPRESSION_COUNTER_SERVICE)
private readonly expressionCounterService: ExpressionCounterService,
#Inject(REGEXP_CREATOR_SERVICE_INTERFACE)
private readonly regExCreatorService: RegExCreatorService,
) {}
getResult(expression: string): string {
//some code return string
}
exponentialToDecimal(string): {
//some code return string
}
}
DefaultService is the constructor while that T would represent an instance of T, that's why you got that error. You could use the Type<T> utility from #nestjs/common or write your own.
Language=Typescript
I want to use the aggregation of 2 interfaces as the value of an indexable-type in a 3rd interface.
Interface 1:
export interface Employee {
id: string
name: string
}
Interface 2:
export interface Department {
department: string
}
Now I want to write an interface equivalent of this:
export interface EmployeeDetails {
employees: {
[key: string]: {
employeeDetails: EmployeeWithDepartment
}
}
}
where EmployeeWithDepartment is:
export interface EmployeeWithDepartment extends Employee {
departmentDetails: Department
}
Is there a way I can create the EmployeeDetails interface without actually creating EmployeeWithDepartment? Some way to include both Employee and Department at once in the EmployeeDetails interface?
PS: I've been using JS & TypeScript only for a week now, so I may not be aware of some concepts that can easily accomplish this.
I believe that what you are looking for is a type intersection, the & opertator. It combines all properties of two types.
For example:
interface A { a: number }
interface B = { b: string }
type C = A & B // { a: number, b: string }
To use that here in your types, you could do something like:
export interface EmployeeDetails {
employees: {
[key: string]: {
employeeDetails: Employee & { departmentDetails: Department }
}
}
}
Playground
This is probably a good page to read: https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#interfaces
I have abstract class:
abstract class DataType<D> {
constructor(protected data: D) {}
...other
}
and class extended by my abstract class:
type PersonData = {
firstName: string;
lastName: string;
...other props
}
class Person extends DataType<PersonData> {
...methods with body specific for person
}
I need save class Person (and other) in const like that:
const DataClasses: DataConstructor = {
person: Person
...other
}
i need use it like that:
new DataClasses['person']({...PersonData}) // exacly is same as: new Person({...PersonData})
but I've problem with DataConstructor type. I don't know how do this dynamically. I tried many combinations and the closest is:
type DataConstructor = {
[name: string]: new (data: ConstructorParameters<typeof Person>[0]) =>
DataType<ConstructorParameters<typeof Person>[0]>
}
but I must add Person class in typeof. I need this dynamically. DataConstructor must accept any class extends by DataType and returns object to use that: new DataClasses['person'](PersonDataObjec). All clasess has only one property in constructor - data.
I have a third party class object and I want to make the properties of the object readonly. So I can not mutate the state with this.state.object.props = "xx";
It looks like this
class ThirdPartyObject {
id?: string;
}
interface ComponentState {
readonly object: ThirdPartyObject;
}
this.state.object = null; // not possible
this.state.object.id = "newId"; // should also not be possible
How can I do that?
If you want ThirdPartyObject fields to be readonly use Readonly type like:
class ThirdPartyObject {
id?: string;
}
interface ComponentState {
readonly object: Readonly<ThirdPartyObject>;
}
Then this:
this.state.object.id = 'newId';
would throw an error.
I have a typescript interface like so:
interface DataSku {
fields: {
sku: string;
}
}
interface PostProduct {
fields: {
title: string;
skus: DataSku[];
}
}
Now I want to extend the Product interface so that each object in the skus array has an extra field. So I tried this:
interface Sku extends DataSku {
stripe: Stripe.skus.ISku;
}
interface Product extends PostProduct {
fields: PostProduct['fields'] & {
skus: Sku[];
}
}
In my code, I try a loop like so:
(product as Product).fields.skus.forEach(sku => console.log(sku.stripe));
This throws the following Typescript error:
Property 'stripe' does not exist on type 'DataSku'.
Have I extended the interface wrong? The console does output my stripe object as expected, so it's just Typescript that isn't happy with the definition.
A much more elegant (and working) approach for this would be to use generics.
Update PostProduct with a generic parameter that's used for the type of fields.skus:
interface PostProduct<T extends DataSku = DataSku> {
fields: {
title: string;
skus: T[];
}
}
T extends DataSku means the type has to be a subtype of DataSku. And we can even set a default value for T so PostProduct can also be used without having to specify the generic parameter.
Now, for Product we just have to pass Sku as generic parameter:
interface Product extends PostProduct<Sku> {}
Playground
That said, if you want to it without generics and without modifying DataSku and PostProduct. You could do this:
interface Product extends PostProduct {
fields: Omit<PostProduct['fields'], 'skus'> & {
skus: Sku[];
}
}
Playground