# NPM
npm i dto-schema
# Yarn
yarn add dto-schema
Setup @babel/plugin-proposal-decorators with legacy
mode.
{
"plugins": [["@babel/plugin-proposal-decorators", { "legacy": true }]]
}
Enable experimentalDecorators
property in tsconfig.json
{
"compilerOptions": {
"experimentalDecorators": true
}
}
Current build targets modern browsers that supports ESModules (see the browserlist), so you will need to polyfill required ES2015 features like Map
and WeakMap
and transpile unsupported syntax like classes or arrow functions.
- Traversers
- Decorators
function parseDTO<T extends object>(Cls: DTOConstructor, raw: T | object): T;
Transforms provided raw
value with rules defined in the provided Cls
constructor.
Usage
import { parseDTO, StringProp } from 'dto-schema';
class UserDTO {
@StringProp() guid: string;
@StringProp() name: string;
password: string; // this one is not annotated, so it would not be transformed.
}
expect(parseDTO(UserDTO, {})).toEqual({ guid: '', name: '' });
expect(
parseDTO(UserDTO, { guid: 1, name: 'Leeroy Jenkins', password: 'yolo' }),
).toEqual({ guid: '1', name: 'Leeroy Jenkins' });
const user = new UserDTO();
user.name = 'John';
expect(parseDTO(UserDTO, user)).toEqual({ guid: '', name: 'John' });
function serializeDTO<T extends object>(
Cls: DTOConstructor,
raw: T | object,
): object;
Unlike parseDTO
, serializedDTO
uses DTOSchemaOptions#serialize
method which prepares a valid JSON object.
Check DateProp
and Prop
examples.
interface BooleanPropOptions {
defaultValue?: null | boolean;
}
function BooleanProp(options?: BooleanPropOptions): PropertyDecorator;
Annotates property as a boolean
.
Accepts:
defaultValue
- default value to use when input value isnull
orundefined
Usage
import { parseDTO, BooleanProp } from 'dto-schema';
class UserDTO {
@BooleanProp() isVerified: boolean;
@BooleanProp({ defaultValue: true }) isActive: boolean;
@BooleanProp({ defaultValue: null }) isSubscribed: boolean;
}
expect(parseDTO(UserDTO, {})).toEqual({
isVerified: false,
isActive: true,
isSubscribed: null,
});
expect(
parseDTO(UserDTO, {
isVerified: true,
isActive: false,
isSubscribed: false,
}),
).toEqual({
isVerified: true,
isActive: false,
isSubscribed: false,
});
interface NumberPropOptions {
defaultValue?: null | number;
clampMin?: number;
clampMax?: number;
round?: boolean | 'ceil' | 'floor' | 'trunc';
}
function NumberProp(options?: NumberPropOptions): PropertyDecorator;
Annotates property as a number
.
Accepts:
clampMin
- lower bound of the number to clampclampMax
- upper bound of the number to clampround
- round method to use to adjust a valuedefaultValue
- Default value to use when input value isnull
orundefined
Usage
import { parseDTO, NumberProp } from 'dto-schema';
class ProductFilter {
@NumberProp()
id: number;
@NumberProp({ defaultValue: 1, round: true, clampMin: 1 })
page: number;
@NumberProp({ defaultValue: null, round: true, clampMin: 1, clampMax: 50 })
pageSize: number;
}
expect(parseDTO(ProductFilter, {})).toEqual({
id: NaN,
page: 1,
pageSize: null,
});
expect(
parseDTO(ProductFilter, {
id: 42,
page: 0,
pageSize: 100,
}),
).toEqual({
id: 42,
page: 1,
pageSize: 50,
});
expect(
parseDTO(ProductFilter, {
id: 42,
page: 3.33333,
pageSize: 3.3333,
}),
).toEqual({
id: 42,
page: 3,
pageSize: 3,
});
interface StringPropOptions {
defaultValue?: null | string;
trim?: boolean | 'start' | 'end';
}
function StringProp(options?: StringPropOptions): PropertyDecorator;
Annotates property as a string
.
Accepts:
defaultValue
- default value to use when input value isnull
orundefined
trim
- trim method to adjust input value
Usage
import { parseDTO, StringProp } from 'dto-schema';
class UserDTO {
@StringProp() guid: string;
@StringProp({ trim: true }) name: string;
@StringProp({ trim: true, defaultValue: null }) email: string;
}
expect(parseDTO(UserDTO, {})).toEqual({
guid: '',
name: '',
email: null,
});
expect(
parseDTO(UserDTO, {
guid: '123',
name: ' Leeroy\n ',
email: ' [email protected] \n',
}),
).toEqual({
guid: '123',
name: 'Leeroy',
email: '[email protected]',
});
interface DatePropOptions {
defaultValue?: () => null | number | string | Date;
}
function DateProp(options?: DatePropOptions): PropertyDecorator;
Annotates property as a Date
.
Converted to the ISO
string when serialized.
Accepts:
defaultValue
- default value to use when input value isnull
orundefined
Usage
import { parseDTO, serializeDTO, DateProp } from 'dto-schema';
class PostDTO {
@DateProp() updatedAt: string;
@DateProp({ defaultValue: null }) deletedAt: string;
@DateProp({ defaultValue: () => Date.UTC(2019, 4, 24) })
createdAt: string;
}
expect(parseDTO(PostDTO, {})).toEqual({
updatedAt: new Date(NaN), // Invalid Date,
deletedAt: null,
createdAt: new Date(Date.UTC(2019, 4, 24)),
});
expect(serializeDTO(PostDTO, {})).toEqual({
updatedAt: null, // Invalid Date,
deletedAt: null,
createdAt: '2019-05-24T00:00:00.000Z',
});
expect(
parseDTO(PostDTO, {
createdAt: Date.UTC(2019, 4, 24),
updatedAt: '2019-05-24T00:00:00.000Z',
deletedAt: new Date(Date.UTC(2019, 4, 24)),
}),
).toEqual({
updatedAt: new Date(Date.UTC(2019, 4, 24)),
deletedAt: new Date(Date.UTC(2019, 4, 24)),
createdAt: new Date(Date.UTC(2019, 4, 24)),
});
expect(
serializeDTO(PostDTO, {
createdAt: Date.UTC(2019, 4, 24),
updatedAt: '2019-05-24T00:00:00.000Z',
deletedAt: new Date(Date.UTC(2019, 4, 24)),
}),
).toEqual({
updatedAt: '2019-05-24T00:00:00.000Z',
deletedAt: '2019-05-24T00:00:00.000Z',
createdAt: '2019-05-24T00:00:00.000Z',
});
interface UnknownPropOptions {
defaultValue?: () => null | number | string | Date;
}
function UnknownProp(options?: UnknownPropOptions): PropertyDecorator;
Annotates property as a Date
.
Converted to the ISO
string when serialized.
Accepts:
defaultValue
- default value to use when input value isnull
orundefined
Usage
import { parseDTO, serializeDTO, UnknownProp } from 'dto-schema';
class UserDTO {
@UnknownProp() role: unknown;
}
expect(parseDTO(UserDTO, {}), { role: null });
expect(serializeDTO(UserDTO, {}), { role: null });
expect(parseDTO(UserDTO, { role: 1 }), { role: 1 });
expect(serializeDTO(UserDTO, { role: 1 }), { role: 1 });
expect(parseDTO(UserDTO, { role: 'a' }), { role: 'a' });
expect(serializeDTO(UserDTO, { role: 'a' }), { role: 'a' });
expect(parseDTO(UserDTO, { role: null }), { role: null });
expect(serializeDTO(UserDTO, { role: null }), { role: null });
interface ObjectPropOptions {
nullable?: boolean;
}
function ObjectProp(options?: ObjectPropOptions): PropertyDecorator;
Annotates property as a Object
with another DTO type.
Accepts:
nullable
- converts value tonull
when it'snull
orundefined
, otherwise converts DTO with an empty object
Usage
import { parseDTO, ObjectProp, StringProp } from 'dto-schema';
class UserDTO {
@StringProp() guid: string;
}
class PostDTO {
@ObjectProp(() => UserDTO) createdBy: UserDTO;
@ObjectProp(() => UserDTO, { nullable: true }) updatedBy: null | UserDTO;
}
expect(parseDTO(PostDTO, {})).toEqual({
createdBy: { guid: '' },
updatedBy: null,
});
expect(
parseDTO(PostDTO, {
createdBy: { guid: 123 },
updatedBy: { guid: 456 },
}),
).toEqual({
createdBy: { guid: '123' },
updatedBy: { guid: '456' },
});
interface DTOSchemaOptions<TValue = unknown> {
type: string;
testType: (value: unknown) => boolean;
normalize: (value: unknown) => null | TValue;
serialize?: (value: null | TValue) => unknown;
}
function Prop<T>(options?: DTOSchemaOptions<T>): PropertyDecorator;
Annotates property with with another DTO type.
Accepts:
type
- name of the schema type, used for error messagestestType
- used to validate object type of the schemanormalize
- used to convert input value to requiredserialize
- used to convert value to JSON value
Usage
import { parseDTO, serializeDTO, Prop } from 'dto-schema';
class UserFilter {
@Prop<Set<string>>({
type: 'set',
testType(value) {
return value instanceof Set;
},
normalize(value) {
return value instanceof Set
? value
: Array.isArray(value)
? new Set(value)
: new Set();
},
serialize(value) {
return value == null ? [] : Array.from(value);
},
})
roles: Set<string>;
}
expect(parseDTO(UserFilter, {})).toEqual({
roles: new Set(),
});
expect(serializeDTO(UserFilter, {})).toEqual({
roles: [],
});
expect(
parseDTO(UserFilter, {
roles: ['admin', 'editor', 'admin'],
}),
).toEqual({
roles: new Set(['admin', 'editor']),
});
expect(serializeDTO(UserFilter, {})).toEqual({
roles: ['admin', 'editor'],
});
interface ArrayPropOptions<T> {
defaultValue?: () => null | T[];
}
function ArrayProp<T>(options?: ArrayPropOptions<T>): PropertyDecorator;
Annotates property as an Array
, used in combination with another decorator.
Accepts:
defaultValue
- default value to use when input value isnull
orundefined
Usage
import { parseDTO, ArrayProp, StringProp } from 'dto-schema';
class PostDTO {
@ArrayProp({ defaultValue: () => null }) meta: unknown[];
@ArrayProp({ defaultValue: () => ['News'] }) @StringProp() tags: string[];
}
expect(parseDTO(PostDTO, {})).toEqual({
meta: null,
tags: ['News'],
});
expect(
parseDTO(PostDTO, {
meta: ['this', null, 'can', undefined, 'be', NaN, 'anything', true],
tags: ['this', null, 'will', undefined, 'be', NaN, 'transformed', true],
}),
).toEqual({
meta: ['this', null, 'can', undefined, 'be', NaN, 'anything', true],
tags: ['this', '', 'will', '', 'be', 'NaN', 'transformed', 'true'],
});