npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

class-formatter

v3.3.1

Published

一套装饰器风格的数据格式化方法。

Downloads

41

Readme

class-formatter 使用文档

注意:

当前版本仅支持 stage1 阶段的装饰器提案,若您想支持 stage3,请将 class-formatter 升级到 5.0.0 及以上的版本

目录

简介

一套装饰器风格的数据格式化方法。

根据模板针对数据的每个字段进行格式化。

使用场景

复杂数据结构格式化

在一些复杂场景中(例如:大表单数据提交),会遇到层层嵌套的复杂数据结构,且不同字段之间拥有复杂的联动关系。 class-formatter 可以通过装饰器简化这种格式化过程,减轻心智负担,将格式化从业务逻辑中抽离出来

安装

npm install class-formatter

tsconfig.json 需进行如下配置:

{
    "compilerOptions": {
        ...
        "experimentalDecorators": true,
        "emitDecoratorMetadata": true,
        ...
    },
    ...
}

名称释义

  • 模板: 类即是模板。
  • 指令: 模板中属性或方法的 class-formatter 装饰器,一个装饰器即为一个指令。
  • 源数据: 被转换的数据。
  • 转换: 调用 executeTransformexecuteTransArray 函数对源数据进行格式化的行为。

例如:

class User {
    @toString()
    name!: string;

    @toNumber()
    age!: number;
}

const user = {
    name: '张三',
    age: 18
};

const result = executeTransform(User, user);
  • 模板:上述示例中,类 User 即为模板,也是转换函数(executeTransform)的第一个参数。
  • 指令:上述示例中,@toString()@toNumber() 即为指令。
  • 源数据:上述示例中,对象 user 即为源数据。

使用方法

普通对象

class User {
    @toString()
    name!: string;

    @toNumber()
    age!: number;
}

const user = {
    name: '张三',
    age: '18'
};

const formatUser = executeTransform(User, user);
// => { name: '张三', age: 18 }

在上述示例中,formatUser 一定拥有 字符串类型 属性 name数字类型 属性 age

数组

class User {
    @toString()
    name!: string;

    @toNumber()
    age!: number;
}

const users = [{
    name: '张三',
    age: '18'
}];

const formatUsers = executeTransArray(User, users);

若源数据为数组,则可以使用 executeTransArray 进行格式化。

默认值

class User {
    @toString()
    name: string = '张三';

    @toNumber(1)
    age!: number;
}
  • 若源数据中不存在属性,则会根据默认值生成该属性。
    • 例如根据上述模板,若源数据不存在 name 属性,则转换结果一定拥有字符串属性 name,且 name 属性值为 '张三'。
  • 默认值有两种传入方式,模板传入与指令传入,其中模板传入优先级 高于 指令传入。
    • 上述模板中,name 为模板传入,age 为指令传入。

API

|属性装饰器|说明|类型|默认值| |---|---|---|---| |toNumber|若属性为非数字类型,则将属性转换为 number 类型。autoTranstrue 时会自动将字符串转换为数字。|(value?: NumberConfig | number) => Decorator|defaultValue: 0autoTrans: true| |toString|若属性为非字符串类型,则将属性转换为 string 类型。autoTranstrue 时会自动将数字转换为字符串。|(value?: StringConfig | string) => Decorator|defaultValue: ''autoTrans: true| |toBoolean|若属性为非布尔类型,则将属性转换为 boolean 类型|(value?: BooleanConfig | boolean) => Decorator|defaultValue: false| |toSymbol|若属性为非 symbol 类型,则将属性转换为 symbol 类型|(value?: SymbolConfig | symbol) => Decorator|defaultValue: Symbol()| |toRegExp|若属性为非正则类型,则将属性转换为正则类型|(value?: RegConfig | RegExp | string) => Decorator|defaultValue: new RegExp('')| |toType|若属性为非对象类型,则将属性转换为对象。若指定了 Type,则可以将类型转换为 Type 的类型。|(value?: ObjectConfig | Type) => Decorator|defaultValue: {}| |toArray|若属性为非数组类型,则将属性转换为数组。若指定了 Type,则可以将数组内所有数据转换为 Type 的类型。|(value?: ArrayConfig | Type) => Decorator|defaultValue: []| |toKeep|保持源数据引用|keys?: ModelKey | ModelKey[]) => Decorator|--| |Remove|移除属性|(value?: RemoveConfig | RemoveCallback | ModelKey | ModelKey[]) => Decorator|--| |Format|对属性进行自定义格式化。注意:Format 会在所有内置校验结束后执行,且不限制返回值类型,使用时请格外注意|(callback: FormatCallback, keys?: ModelKey | ModelKey[]) => Decorator|--| |Rename|对属性重命名。|(name: string, keys?: ModelKey | ModelKey[]) => Decorator|--|

|方法装饰器|说明|类型|默认值| |---|---|---|---| |ExtendMethod|在结果中继承被装饰的方法或访问器|(keys?: ModelKey | ModelKey[]) => MethodDecorator|--|

|类装饰器|说明|类型|默认值| |---|---|---|---| |Extend|继承父类的全部装饰器|(parent: Type) => ClassDecorator|--| |Mixins|混入,同时继承全部类的装饰器|(...parents: Type[]) => ClassDecorator|--|

executeTransform |参数名称|说明|类型| |---|---|---| |ClassType|模板类|Type| |values|被格式化对象|any| |options|配置项|Omit<FormatOptions, 'map'>|

executeTransArray |参数名称|说明|类型| |---|---|---| |ClassType|模板类|Type| |values|被格式化对象|any[]| |options|配置项|FormatOptions|

Interface

Decorator

type Decorator =  (target: any, propertyKey: string) => void;

NumberConfig

type NumberConfig = { 
    defaultValue?: number;
    autoTrans?: boolean;
    keys?: ModelKey | ModelKey[];
};

StringConfig

type StringConfig = { 
    defaultValue?: string;
    autoTrans?: boolean;
    keys?: ModelKey | ModelKey[];
};

BooleanConfig

type BooleanConfig = { 
    defaultValue?: boolean;
    keys?: ModelKey | ModelKey[]; 
};

SymbolConfig

type SymbolConfig = { 
    defaultValue?: symbol;
    keys?: ModelKey | ModelKey[]; 
};

RegConfig

type RegConfig = { 
    defaultValue?: Regexp | string;
    keys?: ModelKey | ModelKey[]; 
};

ObjectConfig

type ObjectConfig<T = any> = { 
    defaultValue?: Partial<T>; 
    ClassType?: Type<T>;
    keys?: ModelKey | ModelKey[];
};

ArrayConfig

type ArrayConfig<T = any> = { 
    defaultValue?: Partial<T>[]; 
    ClassType?: Type<T>;
    keys?: ModelKey | ModelKey[];
    map?: (value: T, index: number, array: T[]) => T;
};

FormatCallback

type FormatCallback = (item, values) => any;

|名称|说明| |---|---| |item|属性被转换后的值| |values|源数据注意:values 源数据的直接引用,请勿在转换过程中对其进行修改| |shareValue|共享数据注意:shareValue 为共享数据的直接引用,请勿在转换过程中对其进行修改|

RemoveCallback

type RemoveCallback = (value: any, target: Readonly<any>, shareValue?: any) => boolean;

RemoveConfig

type RemoveConfig = {
    beforeRemove?: RemoveCallback;
    keys?: ModelKey | ModelKey[];
};

Type

interface Type<T = any> extends Function {
    new(...args: any[]): T;
}

ModelKey 执行键类型

type ModelKey = string | number;

FormatOptions

type FormatOptions<T> = {
    mergeSource?: boolean;
    key?: ModelKey;
    shareValue?: any;
    deep?: number;
    map?: (value: T, index: number, array: T[]) => T;
}

|名称|说明|类型| |---|---|---| |mergeSource|是否将 源数据 合并到转换结果中|boolean| |key|执行键|ModelKey| |shareValue|共享数据。可在自定义装饰器与 Format 中获取的额外数据|any| |deep|转换深度限制。详情 |boolean| |map|原生数组的 map 方法。仅在 executeTransArray 中生效|(value: T, index: number, array: T[]) => T|

自定义属性装饰器

const CustomDecorator = createFormatDecorator((values, shareValue, ...args) => {
    // ...Do something
    return values.name;
});

// type CustomeDecorator = (...args) => DecoratorFun

class User {
    @CustomDecorator('Hello')
    name!: string;
}

createFormatDecorator |属性|说明|类型| |---|---|---| |callback|装饰器执行回调|(values, shareValue, ...args) => any| |keys|可选参数 执行键|ModelKey | ModelKey[]|

callback |属性|说明|类型| |---|---|---| |values|源数据注意:values 为源数据的直接引用,请勿在转换过程中对其进行修改|any| |shareValue|共享数据注意:shareValue 为共享数据的直接引用,请勿在转换过程中对其进行修改|any| |args|在生成的装饰器中传入的参数|any[]|

关于执行顺序

所有指令丛上到下依次执行,指令格式化的结果会被传递给下一个指令。

const toAge = createFormatDecorator((values: Test) => {
    return 9;
});

class Test {
    @toAge()            // 9
    @toBoolean()        // false
    @toString('7')      // '7'
    @toNumber(5)        // 7
    @Format(v => v + 1) // 8
    @Format(v => v - 7) // 1
    age!: number;
}

const res = executeTransform(Test, {});
console.log(res);   // { age: 1 }

关于执行键

executeTransformoptions 属性中提供了 key 属性,以下称为 rootKey 。 在所有指令中均提供了 keys 属性的入口,以下称为 propertyKeys

  • rootKey 不存在,则会执行所有不存在 propertyKeys 的指令。
  • rootKeypropertyKeys 同时存在,仅有 propertyKeys 包含 rootKey 的指令会被执行。
  • 不存在 propertyKeys 的指令会被无条件执行。
class User {
    @toString({ keys: 'submit' })
    name!: string;

    @toNumber()
    age!: number;
}

const user = {
    name: '张三',
    age: '18'
};

const formatUser = executeTransform(User, user, {
    key: 'submit'
});

上述示例中:

  • toNumber 指令会无条件执行。
  • executeTransform 中传入的 key'submit',则 toString 指令会被执行,否则将忽略 name 属性。

由于执行键的存在,我们可以方便的在同一个模板上定制多套格式化方法。如下:

class User {
    @toString()
    @toString({ defaultValue: '张三', keys: '1' })
    @toString({ defaultValue: '李四', keys: '2' })
    @toString({ defaultValue: '王五', keys: '3' })
    name!: string;

    @toNumber()
    age!: number;
}

注意:当一个属性拥有多个装饰器时,模板的可读性下降,且难以迭代。因此建议优先考虑模板继承策略进行个性化复用。如无必要请尽量避免使用执行键。

多装饰器管理

class-formatter 提供了 createBatchDecorators 方法用于对多装饰器进行管理。

createBatchDecorators |属性|说明|类型| |---|---|---| |...decorators|需要统一管理的装饰器|PropertyDecorator[]|

通过 createBatchDecorators 方法,我们可以对上述案例进行管理:


const NameManage = createBatchDecorators(
    toString({ defaultValue: '张三', keys: '1' }),
    toString({ defaultValue: '李四', keys: '2' }),
    toString({ defaultValue: '王五', keys: '3' })
);

class User {
    @toString()
    @NameManage()
    name!: string;

    @toNumber()
    age!: number;
}

如上将所有拥有执行键的装饰器封装成 NameManage 装饰器,toString 作为默认格式化指令,NameManage 则根据执行键分发指令。

关于循环引用

  • 若多个模板间存在循环引用,则子模板中引用的父模板失效( typescript 自身限制)。
  • 若模板自身循环引用,则默认可转化深度为 50,超过该深度的转换会被忽略。
  • 若被转换对象存在循环引用,则忽略循环属性。

模板自循环

class Person {
    @toString()
    name!: string;

    @toType(Person)
    child!: Person;
}

const target = {
    name: '父亲',
    child: {
        name: 1
    }
}

// target.child 会被忽略
executeTransform(Person, {});

模板自循环理论上允许存在,但可能导致死循环。 为防止这种情况,且保证格式化顺利进行,class-formatter 限制了执行嵌套深度,默认 50。超过深度的数据会停止转换。 可以通过 options.deep 自行调整深度。

死循环例:

class Person {
    @toString()
    name!: string;

    @toArray(Person)
    childs: Person = [{}];
}

executeTransArray(Person, [{}], {
    deep: 50
});

关于混入

为实现更加灵活的模板组合方案,class-formatter 提供 mixins 方法实现多模板组合。

例如:

class A {
    @toString()
    a!: string;
}

class B {
    @toString()
    b!: string;
}

class C implements A, B {
    a!: string;
    b!: string;

    @toString()
    c!: string;
}

mixins(C, [A, B]);

如此 C 便即继承了 AB 的全部指令。

同时提供了 Mixins 类装饰器来简化混入。

class A {
    @toString('A')
    name!: string;
}

class B {
    @toString('B')
    name!: string;
}

@Mixins(A, B)
class C implements A, B {
    @toString()
    c!: string;

    name!: string;
}

const res = executeTransform(C, {});
// res => { name: 'A', c: '' }

Mixins 传参拥有优先级,当多个模板中存在相同的属性时,后面参数的指令将会 完全覆盖 前一个参数的指令。 上述示例中,A 模板的 name 属性的指令将会被 B 模板的 name 完全覆盖。

注意事项

关于使用

  • 所有转化规则均依赖指令,所有拥有指令的属性、方法、访问器均会被转换,其余属性、方法、访问器会被忽略。
  • 指令可以通过 Extend 在多个模板间继承。
  • 多个模板可通过 Mixins 组合成一个大模板,同时共享全部指令。
  • 子模板中声名的同名属性若拥有指令,则子模板的指令将会 完全覆盖 继承的指令。(即重写指令)
  • 子模板的 模板默认值 会覆盖父模板的 模板默认值