You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在NestJS/Prisma/GraphQL中Decimal转number失败的问题

问题描述

我有一个类型为Decimal的字段,从客户端获取数据时返回的是string类型,但预期应为number类型。

对象类型定义:

import { Decimal } from '@prisma/client/runtime/library';
import { GraphQLDecimal } from 'prisma-graphql-type-decimal';

@ObjectType()
export class Supplier {
    @Field(() => GraphQLDecimal, {nullable:false})
    commission!: Decimal;
}

返回结果:

{
    // 其他字段....
    id: 7;
    isConfirmed: false;
    webAppLanguage: 'fr';

    // 应为number类型
    commission: '0.3';
}

我尝试通过NestJS字段级中间件将Decimal转为number,但未生效:

export const decimalCastMiddleWare: FieldMiddleware = async (
    ctx: MiddlewareContext,
    next: NextFn,
) => {
    let value = await next();

    if (Decimal.isDecimal(value)) {
        value = value.toNumber();
    }

    return value;
};

// 字段级中间件配置
{
    buildSchemaOptions: {
        // 其他ApolloDriverConfig配置项...
        fieldMiddleware: [decimalCastMiddleWare]
    }
}
问题原因

你使用的GraphQLDecimal标量默认会把Decimal对象序列化为string返回,所以字段中间件拿到的value已经是string类型,导致Decimal.isDecimal(value)判断不成立,转换逻辑无法执行。

解决方案

方案一:自定义标量类型替换GraphQLDecimal

自己实现一个标量,在序列化阶段直接把Decimal转成number返回:

import { Scalar, CustomScalar } from '@nestjs/graphql';
import { Decimal } from '@prisma/client/runtime/library';
import { ValueNode, Kind } from 'graphql';

@Scalar('DecimalNumber', () => Number)
export class DecimalNumberScalar implements CustomScalar<Decimal, number> {
  description = 'Decimal转number的自定义标量';

  serialize(value: Decimal): number {
    return value.toNumber();
  }

  parseValue(value: string | number): Decimal {
    return new Decimal(value);
  }

  parseLiteral(ast: ValueNode): Decimal {
    if (ast.kind === Kind.FLOAT || ast.kind === Kind.INT) {
      return new Decimal(ast.value);
    }
    throw new Error('该标量仅支持解析浮点数/整数字面量');
  }
}

在模块中注册这个标量,然后修改Supplier类的字段定义:

@ObjectType()
export class Supplier {
    @Field(() => DecimalNumberScalar, {nullable:false})
    commission!: Decimal;
}

方案二:调整中间件逻辑兼容string转number

如果不想自定义标量,可以修改中间件,增加对string类型的判断转换:

export const decimalCastMiddleWare: FieldMiddleware = async (
    ctx: MiddlewareContext,
    next: NextFn,
) => {
    let value = await next();

    if (Decimal.isDecimal(value)) {
        value = value.toNumber();
    } else if (typeof value === 'string' && !isNaN(parseFloat(value))) {
        value = parseFloat(value);
    }

    return value;
};

方案三:Prisma查询阶段直接转换

在查询数据时,直接把Decimal字段转为number:

// 示例查询逻辑
const supplier = await prisma.supplier.findUnique({
  where: { id: 7 },
  select: {
    id: true,
    isConfirmed: true,
    webAppLanguage: true,
    commission: true,
  },
});

if (supplier) {
  supplier.commission = supplier.commission.toNumber();
}
注意事项
  • 使用toNumber()转换Decimal可能丢失精度(比如极大/极小的小数),如果业务对精度要求高,建议保留string返回,由客户端自行处理。
  • 自定义标量时,要确保客户端传入的参数能正确转换为Decimal,避免数据异常。

内容的提问来源于stack exchange,提问作者Meds

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 20:25:12