自定义标量
TheGraphQL规范包括默认的标量类型Int
, Float
, String
, Boolean
和ID
。虽然这些标量涵盖了大多数用例,但某些应用程序需要支持其他原子数据类型(例如Date
)或在现有类型中添加验证。为此,您可以为标量类型定义自定义类型。
定义自定义标量
要定义一个自定义标量,您可以将其添加到您的模式中,如下所示:
scalar MyCustomScalar
现在您可以在任何可以使用默认标量的地方使用MyCustomScalar
(例如:作为对象字段、输入类型字段或参数)的类型。
包含标量规范
更新于2021年10月的GraphQL规范版本中,您可以将@specifiedBy
指令用作元数据,供模式消费者了解标量使用的格式。此指令不提供自动验证,但可以为人阅读模式提供有用的上下文。
scalar MyCustomScalar @specifiedBy(url: "https://specs.example.com/rfc111")
然而,Apollo Server仍然需要知道如何交互和生成此新标量类型的值。
定义自定义标量逻辑
在你定义了一个自定义标量类型之后,你需要定义Apollo Server如何与之交互。特别是,你需要定义:
- 标量的值在您的后端如何表示
- 这通常是后端数据存储驱动程序使用的表示。
- 如何将值的后端表示序列化到兼容JSON的类型
- 如何将兼容JSON的表示反序列化到后端表示
你定义这些交互在一个GraphQLScalarType
类的实例中。
有关graphql
库的类型系统的更多信息,请参阅官方文档。
示例:Date
标量
ⓘ 注意
下面的代码块默认使用TypeScript。您可以通过每个代码块上方的下拉菜单将其切换到JavaScript。
如果您使用的是JavaScript,请使用.js
和.jsx
文件
以下GraphQLScalarType
对象定义了一个表示日期的自定义标量(这是最常见的自定义标量之一)的交互。它假定我们的后端使用JavaScript对象Date
表示日期。
import { GraphQLScalarType, Kind } from 'graphql';const dateScalar = new GraphQLScalarType({name: 'Date',description: 'Date custom scalar type',serialize(value) {if (value instanceof Date) {return value.getTime(); // Convert outgoing Date to integer for JSON}throw Error('GraphQL Date Scalar serializer expected a `Date` object');},parseValue(value) {if (typeof value === 'number') {return new Date(value); // Convert incoming integer to Date}throw new Error('GraphQL Date Scalar parser expected a `number`');},parseLiteral(ast) {if (ast.kind === Kind.INT) {// Convert hard-coded AST string to integer and then to Datereturn new Date(parseInt(ast.value, 10));}// Invalid hard-coded value (not an integer)return null;},});
import { GraphQLScalarType, Kind } from 'graphql';const dateScalar = new GraphQLScalarType({name: 'Date',description: 'Date custom scalar type',serialize(value) {if (value instanceof Date) {return value.getTime(); // Convert outgoing Date to integer for JSON}throw Error('GraphQL Date Scalar serializer expected a `Date` object');},parseValue(value) {if (typeof value === 'number') {return new Date(value); // Convert incoming integer to Date}throw new Error('GraphQL Date Scalar parser expected a `number`');},parseLiteral(ast) {if (ast.kind === Kind.INT) {// Convert hard-coded AST string to integer and then to Datereturn new Date(parseInt(ast.value, 10));}// Invalid hard-coded value (not an integer)return null;},});
这个初始化定义了以下方法:
serialize
parseValue
parseLiteral
这些方法共同描述了 Apollo Server 在每个场景中如何与标量交互。
serialize
serialize
方法将标量的后端表示形式转换为 JSON 兼容格式,以便 Apollo Server 可以将其包含在操作响应中。
在上面的示例中,日期标量在后端通过 JavaScript 对象代表。当我们发送 GraphQL 响应中的日期标量时,我们将其序列化为 JavaScript Date 对象的 getTime() 函数返回的整数值。
请注意,Apollo Client 不能自动解析自定义标量(参见问题),因此您的客户端必须定义自定义逻辑来按需反序列化此值。
parseValue
parseValue
方法在将其添加到解析器的 args 之前将标量的 JSON 值转换为后端表示形式。
当客户端通过 GraphQL 变量GraphQL variable提供标量(scalar)时,Apollo Server 会调用这个方法。
parseLiteral
当传入的 查询 字符串将标量作为硬编码的 参数 值时,该值将是查询 文档 的抽象语法树(AST)的一部分。Apollo Server 会调用 parseLiteral
方法将值的 AST 表示形式转换为标量的后端表示形式。
在上面的例子中,parseLiteral
将从字符串到整数的 AST 值进行转换,然后转换为 Date
对象以匹配 parseValue
的结果。
为 Apollo Server 提供自定义标量
在定义您的 GraphQLScalarType
实例后,您可以将其包含在同一个 解析器映射 中,该映射包含了您的模式中其他类型和 字段 的解析器:
import { ApolloServer } from '@apollo/server';import { startStandaloneServer } from '@apollo/server/standalone';import { GraphQLScalarType, Kind } from 'graphql';const typeDefs = `#graphql/* highlight-line */ scalar Datetype Event {id: ID!date: Date!}type Query {events: [Event!]}`;const dateScalar = new GraphQLScalarType({// See definition above});const resolvers = {Date: dateScalar,// ...other resolver definitions...};const server = new ApolloServer({typeDefs,resolvers,});const { url } = await startStandaloneServer(server);console.log(`🚀 Server listening at: ${url}`);
import { ApolloServer } from '@apollo/server';import { startStandaloneServer } from '@apollo/server/standalone';import { GraphQLScalarType } from 'graphql';const typeDefs = `#graphql/* highlight-line */ scalar Datetype Event {id: ID!date: Date!}type Query {events: [Event!]}`;const dateScalar = new GraphQLScalarType({// See definition above});const resolvers = {Date: dateScalar,// ...other resolver definitions...};const server = new ApolloServer({typeDefs,resolvers,});const { url } = await startStandaloneServer(server);console.log(`🚀 Server listening at: ${url}`);
示例:将整数限制为奇数
在此示例中,我们创建了一个名为 Odd
的自定义标量,它只能包含奇数整数:
import { ApolloServer } from '@apollo/server';import { startStandaloneServer } from '@apollo/server/standalone';import { GraphQLScalarType, Kind, GraphQLError } from 'graphql';// Basic schemaconst typeDefs = `#graphqlscalar Oddtype Query {# Echoes the provided odd integerechoOdd(odd: Odd!): Odd!}`;// Validation function for checking "oddness"function oddValue(value: unknown) {if (typeof value === 'number' && Number.isInteger(value) && value % 2 !== 0) {return value;}throw new GraphQLError('Provided value is not an odd integer', {extensions: { code: 'BAD_USER_INPUT' },});}const resolvers = {Odd: new GraphQLScalarType({name: 'Odd',description: 'Odd custom scalar type',parseValue: oddValue,serialize: oddValue,parseLiteral(ast) {if (ast.kind === Kind.INT) {return oddValue(parseInt(ast.value, 10));}throw new GraphQLError('Provided value is not an odd integer', {extensions: { code: 'BAD_USER_INPUT' },});},}),Query: {echoOdd(_, { odd }) {return odd;},},};const server = new ApolloServer({typeDefs,resolvers,});const { url } = await startStandaloneServer(server);console.log(`🚀 Server listening at: ${url}`);
import { ApolloServer } from '@apollo/server';import { startStandaloneServer } from '@apollo/server/standalone';import { GraphQLScalarType, Kind, GraphQLError } from 'graphql';// Basic schemaconst typeDefs = `#graphqlscalar Oddtype Query {# Echoes the provided odd integerechoOdd(odd: Odd!): Odd!}`;// Validation function for checking "oddness"function oddValue(value) {if (typeof value === 'number' && Number.isInteger(value) && value % 2 !== 0) {return value;}throw new GraphQLError('Provided value is not an odd integer', {extensions: { code: 'BAD_USER_INPUT' },});}const resolvers = {Odd: new GraphQLScalarType({name: 'Odd',description: 'Odd custom scalar type',parseValue: oddValue,serialize: oddValue,parseLiteral(ast) {if (ast.kind === Kind.INT) {return oddValue(parseInt(ast.value, 10));}throw new GraphQLError('Provided value is not an odd integer', {extensions: { code: 'BAD_USER_INPUT' },});},}),Query: {echoOdd(_, { odd }) {return odd;},},};const server = new ApolloServer({typeDefs,resolvers,});const { url } = await startStandaloneServer(server);console.log(`🚀 Server listening at: ${url}`);
导入第三方自定义标量
如果其他库定义了自定义标量,您可以按与其他符号一样的方式导入并使用它。
例如,graphql-type-json
库定义了GraphQLJSON
对象,它是GraphQLScalarType
的一个实例。您可以使用该对象定义一个接受任何有效JSON值的JSON
标量。
首先,安装库
$ npm install graphql-type-json
然后导入GraphQLJSON
对象并将其添加到解析器映射中,就像平常一样:
import { ApolloServer } from '@apollo/server';import { startStandaloneServer } from '@apollo/server/standalone';import GraphQLJSON from 'graphql-type-json';const typeDefs = `#graphqlscalar JSONtype MyObject {myField: JSON}type Query {objects: [MyObject]}`;const resolvers = {JSON: GraphQLJSON,// ...other resolvers...};const server = new ApolloServer({typeDefs,resolvers,});const { url } = await startStandaloneServer(server);console.log(`🚀 Server listening at: ${url}`);