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

如何在Apollo Client中解析后端返回的自定义Decimal标量?

GraphQL自定义Decimal标量在Apollo Client中的解析问题

问题背景

后端GraphQL服务定义了Decimal自定义标量,Product类型的price字段使用该类型:

scalar Decimal

type Product {
  id: ID!
  price: Decimal
}

后端Decimal标量解析器实现:

const decimalScalar = new GraphQLScalarType({
  name: "Decimal",
  description: "Decimal custom scalar type",
  serialize(value: number) {
    return new Decimal(value);
  },
  parseValue(value: number) {
    return new Decimal(value);
  },
  parseLiteral(ast) {
    if (ast.kind === Kind.FLOAT) {
      return new Decimal(parseFloat(ast.value));
    }
    return null;
  }
});

后端返回的JSON数据示例:

[
  {
    "id": "product_1",
    "price": "12.22" // 是否需要返回__typename: Decimal?
  },
  {
    "id": "product_2",
    "price": "18.05"
  }
]

问题现象

前端Apollo Client接收数据后,price字段仍为字符串类型:

console.log(typeof data?.getProducts?.[0].price) // string(TypeScript显示为any类型)

已尝试的方案

客户端配置了Decimal标量解析逻辑,但未生效:

// DecimalScalar.ts
import { GraphQLScalarType, Kind } from 'graphql';
import Decimal from 'decimal.js';

const decimalScalar = new GraphQLScalarType({
  name: 'Decimal',
  description: 'Decimal custom scalar type',
  serialize(value: number) {
    return new Decimal(value);
  },
  parseValue(value: number) {
    return new Decimal(value);
  },
  parseLiteral(ast) {
    if (ast.kind === Kind.FLOAT) {
      return new Decimal(parseFloat(ast.value));
    }
    return null;
  }
});

export default decimalScalar;


// Apollo Client
const retryLink = new RetryLink();
const httpLink = new HttpLink({ uri: config.API_URL })
const apolloLink = new ApolloLink((operation, forward) => {
  operation.setContext({
    customScalars: {
      Decimal: {
        serialize: (value: number) => value.toString(),
        parseValue: (value: number) => new Decimal(value),
        parseLiteral: decimalScalar.parseLiteral,
      },
    },
  });
  return forward(operation);
})

const client = new ApolloClient({
  cache: new InMemoryCache(),
  link: ApolloLink.from([
    retryLink,
    httpLink,
    apolloLink
  ])
});

疑问解答

1. Apollo如何识别需将price解析为Decimal标量?

Apollo Client是通过GraphQL Schema的类型定义识别字段类型的,不需要标量值携带__typename。只要客户端能获取到完整Schema(通过服务端 introspection 或本地定义typeDefs),就知道Product的price字段是Decimal类型,会自动调用对应的标量解析器处理该字段的值。

2. 后端返回的price仅为字符串是否正确?是否需携带__typename: "Decimal"?

  • 后端返回字符串是正确的:Decimal标量需要序列化为JSON支持的格式(字符串是最优选择,可避免浮点数精度丢失),所以返回字符串没问题。
  • 不需要携带__typename: "Decimal":__typename是GraphQL对象类型的标识,标量类型不属于对象类型,不需要也不支持添加该字段。

另外注意:后端的serialize方法存在问题,当前代码返回new Decimal(value),但serialize的作用是将服务端的Decimal值转换为可JSON序列化的格式,应该改为返回字符串:

serialize(value: Decimal) {
  return value.toString();
}

否则服务端返回的会是Decimal对象,JSON序列化后可能不符合预期。

3. 客户端的Decimal标量解析实现是否正确?

客户端的实现存在多处问题,正确的配置方式如下:

步骤1:修正标量解析器逻辑

// DecimalScalar.ts
import { GraphQLScalarType, Kind } from 'graphql';
import Decimal from 'decimal.js';

export const DecimalScalar = new GraphQLScalarType({
  name: 'Decimal',
  description: 'Decimal custom scalar type',
  // 客户端发送数据时,将Decimal对象转为字符串
  serialize(value: Decimal) {
    return value.toString();
  },
  // 解析服务端返回的字符串为Decimal对象
  parseValue(value: string) {
    return new Decimal(value);
  },
  // 解析查询中的字面量(比如GraphQL查询里写的12.22)
  parseLiteral(ast) {
    if (ast.kind === Kind.FLOAT || ast.kind === Kind.INT) {
      return new Decimal(ast.value);
    }
    return null;
  }
});

步骤2:正确配置Apollo Client

需要在客户端传入本地typeDefs和resolvers,让Apollo识别标量类型并应用解析器:

import { ApolloClient, InMemoryCache, HttpLink, ApolloLink, gql } from '@apollo/client';
import { DecimalScalar } from './DecimalScalar';
import { RetryLink } from '@apollo/client/link/retry';

// 本地定义Schema类型
const typeDefs = gql`
  scalar Decimal

  type Product {
    id: ID!
    price: Decimal
  }

  type Query {
    getProducts: [Product]
  }
`;

// 定义resolvers,指定标量解析器
const resolvers = {
  Decimal: DecimalScalar,
};

const retryLink = new RetryLink();
const httpLink = new HttpLink({ uri: config.API_URL });

const client = new ApolloClient({
  cache: new InMemoryCache(),
  link: ApolloLink.from([retryLink, httpLink]),
  typeDefs, // 传入本地类型定义
  resolvers, // 传入解析器
});

问题点说明:

  • 之前通过operation.context.customScalars配置是无效的,Apollo Client不支持这种方式。
  • 客户端parseValue方法的参数类型错误:服务端返回的是字符串,所以应该接收string类型而非number。
  • 缺少本地typeDefs配置,导致Apollo无法识别Decimal标量类型,自然不会应用解析器。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 00:12:06