Skip to content

question: Anyway to implement PartialType like nestjs for other frameworks #2610

Open
@jeffpham2906

Description

@jeffpham2906

Currently I'm trying to extends a Require Schema class but want to make it reuse by PartialType to prevent redeclare the field.

Anyway to achieve this?

Framework: Express JS + Tsoa
I have tried this but it seem does not work.

// Utility function to create PartialType (similar to NestJS)
export function PartialType<T>(BaseClass: new () => T): new () => Partial<T> {
  class PartialClass {}
  
  // Copy metadata from the base class
  const baseClassPrototype = BaseClass.prototype;
  const partialClassPrototype = PartialClass.prototype;
  
  // Get all property names from the base class
  const propertyNames = Object.getOwnPropertyNames(baseClassPrototype);
  
  // Copy validation metadata for each property
  propertyNames.forEach(propertyName => {
    if (propertyName === 'constructor') return;
    
    // Get existing validation metadata
    const validationMetadata = Reflect.getMetadata('custom:validation', baseClassPrototype, propertyName) || [];
    const typeMetadata = Reflect.getMetadata('design:type', baseClassPrototype, propertyName);
    
    // Apply IsOptional decorator to make the property optional
    IsOptional()(partialClassPrototype, propertyName);
    
    // Preserve existing validation decorators
    if (typeMetadata) {
      Reflect.defineMetadata('design:type', typeMetadata, partialClassPrototype, propertyName);
    }
    
    // Copy validation metadata
    const existingValidators = Reflect.getMetadataKeys(baseClassPrototype, propertyName);
    existingValidators.forEach(key => {
      const metadata = Reflect.getMetadata(key, baseClassPrototype, propertyName);
      if (metadata && key !== 'custom:validation') {
        Reflect.defineMetadata(key, metadata, partialClassPrototype, propertyName);
      }
    });
  });
  
  return PartialClass as new () => Partial<T>;
}

// Alternative simpler implementation using class-validator's internal metadata
export function PartialTypeSimple<T>(BaseClass: new () => T): new () => Partial<T> {
  class PartialClass extends BaseClass {}
  
  // Get all properties from the base class
  const instance = new BaseClass();
  const propertyNames = Object.getOwnPropertyNames(instance);
  
  // Apply IsOptional to each property
  propertyNames.forEach(propertyName => {
    IsOptional()(PartialClass.prototype, propertyName);
  });
  
  return PartialClass as new () => Partial<T>;
}

// Even simpler approach using mixin pattern
export function PartialTypeMixin<T>(BaseClass: new () => T) {
  return class extends BaseClass {
    constructor() {
      super();
      // Apply IsOptional to all properties
      const propertyNames = Object.getOwnPropertyNames(this);
      propertyNames.forEach(propertyName => {
        IsOptional()(this.constructor.prototype, propertyName);
      });
    }
  } as new () => Partial<T>;
}

// Most practical approach - manual but reliable
export function createPartialType<T>(BaseClass: new () => T): new () => Partial<T> {
  // Create a new class that extends the base class
  class PartialClass {}
  
  // Copy the prototype
  Object.setPrototypeOf(PartialClass.prototype, BaseClass.prototype);
  
  // Get all property descriptors
  const baseInstance = new BaseClass();
  const propertyNames = Object.getOwnPropertyNames(baseInstance);
  
  // Make all properties optional
  propertyNames.forEach(propertyName => {
    if (propertyName !== 'constructor') {
      IsOptional()(PartialClass.prototype, propertyName);
    }
  });
  
  return PartialClass as new () => Partial<T>;
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    type: questionQuestions about the usage of the library.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions