Class-transformer: feature: ship common transforms in subdirectory

Created on 3 Aug 2020  路  3Comments  路  Source: typestack/class-transformer

Description


Due to the deprecation of the class-sanitizer package, release common transformations will improve new users experience.

Proposed solution



I've imagined something like:

import { Trim, ToLowerCase } from 'class-transformer/common'

class Person {
  @Trim()
  @ToLowerCase()
  public email!: string;
}

Thus this can be achieved with this piece of code:

import { Trim } from 'class-transformer'

export function Trim() {
  return (target: any, key: string) => {
    return Transform((value: string) => value.trim())(target, key);
  };
}

Or in a more customizable way:

export function Trim(only?: "left" | "right") {
  return (target: any, key: string) => {
    return Transform((value: string) => {
      if (only) {
        if (only === "left") {
          return value.trimLeft();
        }

        return value.trimRight();
      }

      return value.trim();
    })(target, key);
  };
}

If the solution can't be shipped, encourage new users to create your own decorators to avoid mismatch between yours dtos.

Thanks for pay attention, awesome work!

needs discussion feature

Most helpful comment

Better solution

import { Transform, TransformOptions } from 'class-transformer';

export interface TrimOptions {
    /** @default 'both' */
    strategy?: 'start' | 'end' | 'both';
}

export function Trim(options?: TrimOptions, transformOptions?: TransformOptions): (target: any, key: string) => void {
    return Transform((value: unknown) => {
        if ('string' !== typeof value) {
            return value;
        }

        switch (options?.strategy) {
            case 'start':
                return value.trimLeft();
            case 'end':
                return value.trimRight();
            default:
                return value.trim();
        }
    }, transformOptions);
}

Test

import { plainToClass } from 'class-transformer';

describe('#Trim', () => {
    class TestModel {
        @Trim()
        withDefault: string;
        @Trim({ strategy: 'start' })
        trimStart: string;
        @Trim({ strategy: 'end' })
        trimEnd: string;
        @Trim({ strategy: 'both' })
        trimBoth: string;
    }

    it('should transform with success', async () => {
        return plainToClass(TestModel, {
            withDefault: '   withDefault   ',
            trimStart: '   trimStart   ',
            trimEnd: '   trimEnd   ',
            trimBoth: '   trimBoth   ',
        }).should.eql({
            withDefault: 'withDefault',
            trimStart: 'trimStart   ',
            trimEnd: '   trimEnd',
            trimBoth: 'trimBoth',
        });
    });
});

All 3 comments

Yes, this is on the roadmap but we need to figure out what is the best way to ship this. In the meantime feel free to use class-sanitizer the latest version is not deprecated just new development is halted. (I will deprecate only if a security bug arise.)

ping, want this too.

Better solution

import { Transform, TransformOptions } from 'class-transformer';

export interface TrimOptions {
    /** @default 'both' */
    strategy?: 'start' | 'end' | 'both';
}

export function Trim(options?: TrimOptions, transformOptions?: TransformOptions): (target: any, key: string) => void {
    return Transform((value: unknown) => {
        if ('string' !== typeof value) {
            return value;
        }

        switch (options?.strategy) {
            case 'start':
                return value.trimLeft();
            case 'end':
                return value.trimRight();
            default:
                return value.trim();
        }
    }, transformOptions);
}

Test

import { plainToClass } from 'class-transformer';

describe('#Trim', () => {
    class TestModel {
        @Trim()
        withDefault: string;
        @Trim({ strategy: 'start' })
        trimStart: string;
        @Trim({ strategy: 'end' })
        trimEnd: string;
        @Trim({ strategy: 'both' })
        trimBoth: string;
    }

    it('should transform with success', async () => {
        return plainToClass(TestModel, {
            withDefault: '   withDefault   ',
            trimStart: '   trimStart   ',
            trimEnd: '   trimEnd   ',
            trimBoth: '   trimBoth   ',
        }).should.eql({
            withDefault: 'withDefault',
            trimStart: 'trimStart   ',
            trimEnd: '   trimEnd',
            trimBoth: 'trimBoth',
        });
    });
});
Was this page helpful?
0 / 5 - 0 ratings