背景
在使用class-transformer进行对象转换并且遇到类型为Decimal的数据时工作不正常
异常复现
我们有以下类型,是目标对象:
1 | export class OrderItemTransfer { |
以及,源对象定义:
1 | () |
我们的目标是将 OrderItem 类型的对象转换到 OrderItemTransfer 类型。于是有以下代码:
1 | let transfer = plainToClass(OrderItemTransfer, entity); |
异常分析
通过追踪调用栈发现如下信息:

找到调用Decimal构造函数的具体代码

可以看到,这里直接调用了Decimal构造函数,然而Decimal的构造函数并不支持参数是null/undefined从而引发了异常。
plainToClass这个方法的设计是针对plain object到class的转换,通过下图可见,他的类型判断相对简单

在判断对象类型是object之后并没有进一步判断构造函数相关信息(针对plain object的设计并不需要判断,因为这种情况并不存在自定义的构造函数)。但是这种特性恰巧妨碍了我们使用类似于Decimal这种没有默认构造函数的类型。
解决方案
其实2017年已经有人提过这个问题,但是这种情况确实是此方法的设计情况之外,而且官方也并没有给出解决方案。仅有某位用户提供的一个临时性的解决方案:
1 | else if (value[valueKey] instanceof Function) { |
这个解决方案真的能解决问题么?答案是:可以,但不优雅。
与其提交pr给class-transformer不如从另外一个角度来解决,那就是从Decimal.js下手。由于问题发生在Decimal类型没有默认的构造函数,那么我们为何不拓展以下这个类型呢?
1 | class DecimalPatch { |
看到这里,你可能要问几个问题:
为什么不用extends呢?答案是class-transformer拿到的类型仍是Decimal的构造函数
为什么要delete d.constructor呢?答案是防止plainToClass再次调用constructor
(这个方法很迷,它会调用源对象的所有方法求值赋值给目标对象,看似是为了调用getter作用的函数,但是并没有判断能力,就连构造函数也不放过QAQ。。。)
现在,你可以这样使用它
1 | () |
它长这个样子
1 | export class DecimalTransformer implements ValueTransformer { |
那么我们可以修改上述代码:
1 | from(data: string): DecimalPatch { |
这样,entity中的数据实际类型是DecimalPatch,并且工作一切正常