如何在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
相关产品推荐
相关产品推荐

