-
Notifications
You must be signed in to change notification settings - Fork 519
Open
Labels
flag: needs discussionIssues which needs discussion before implementation.Issues which needs discussion before implementation.type: featureIssues related to new features.Issues related to new features.
Description
Description
For my projects i have made a discriminator function to tackle 2 problems with class transformers Discrimination:
- It is not typesafe
- It is very verbose (especially if you combine it with class-validator and nestjs-swagger)
in this Issue i just wanted to share my solution in case some one wants to use it.
Maybe it also helps to further develop the feature.
Proposed solution
I know my solution has dependencies to class-validator and nestjs.
Because this is what is use it with and what is see the most benefit in bringing all three together.
Maybe someone has a smart solution on how such kind of solution can be integerated somewhere :)
How it can be used
I did not want to discriminate any families in the example, but that is what it ended up with.
export class FatherDetails {
readonly type: 'father' = 'father' as const;
readonly foo: string;
}
export class MotherDetails {
readonly type: 'mother' = 'mother' as const;
readonly bar: number;
}
export class ChildDetails {
readonly type: 'child' = 'child' as const;
readonly baz: boolean;
}
const discrimation = createDiscriminationDecorators(
[
{ cls: FatherDetails, value: 'father' }, // Value is `somewhat` type safe only values that any of the classes in the array have can be inserted
{ cls: MotherDetails, value: 'mother' },
{ cls: ChildDetails, value: 'child' },
] as const, // As const is needed for the DiscriminationType to pick up on the different classes
'type', // This is type safe only keys that all classes mentioned above share can be inserted here
);
@discrimation.ApiExtraModels // This is only needed if you use Swagger Documention in Nestjs
export class FamilyMember {
@discrimation.Discriminated
readonly details: DiscrimatorType<typeof discrimation>; // You do not need to type out the classes again here
}
The code
import { applyDecorators } from '@nestjs/common';
import { ApiExtraModels, ApiProperty, getSchemaPath } from '@nestjs/swagger';
import { Type, type ClassConstructor } from 'class-transformer';
import { IsIn, IsNotEmpty, IsObject, ValidateNested } from 'class-validator';
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export function createDiscriminationDecorators<
BaseClass extends object,
T extends {
cls: ClassConstructor<BaseClass>;
value: DiscriminatorValue;
},
Discriminator extends keyof InstanceType<T['cls']> & string,
DiscriminatorValue extends InstanceType<T['cls']>[Discriminator],
>(subClasses: readonly T[], discriminator: Discriminator) {
class Base {
@IsIn(subClasses.map((subClass) => subClass.value))
[discriminator]: DiscriminatorValue;
}
const type : {
keepDiscriminatorProperty: boolean;
discriminator: {
property: Discriminator;
subTypes: {
value: ClassConstructor<BaseClass>;
name: any;
}[];
};
} =
{
keepDiscriminatorProperty: true,
discriminator: {
property: discriminator,
subTypes: subClasses.map((subClass) => ({
value: subClass.cls,
name: subClass.value,
})),
},
};
const apiProperty = {
oneOf: subClasses.map((subClass) => ({
$ref: getSchemaPath(subClass.cls),
})),
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
type: () => Base,
};
return {
ApiExtraModels: applyDecorators(
ApiExtraModels(...subClasses.map((subClass) => subClass.cls)),
),
Discriminated: applyDecorators(
ValidateNested(),
Type(() => Base, type),
IsNotEmpty(),
IsObject(),
ApiProperty(apiProperty),
),
_subClasses: subClasses,
};
}
export type DiscrimatorType<
T extends { _subClasses: readonly { cls: ClassConstructor<any> }[] },
> = InstanceType<T['_subClasses'][number]['cls']>;
export type DiscrimatorValue<T extends { _subClasses: { value: string }[] }> =
T['_subClasses'][number]['value'];
Metadata
Metadata
Assignees
Labels
flag: needs discussionIssues which needs discussion before implementation.Issues which needs discussion before implementation.type: featureIssues related to new features.Issues related to new features.