前端进阶之旅前端进阶之旅
  • 基础篇HTML/CSS/JS 打底
  • 进阶篇原理与工程化
  • 高频篇面试最常问的那批
  • 精选篇按模块收敛的总结
  • 手写篇常考代码手写实现
  • 面经篇真实面试问题复盘
  • AI 篇NEWAI 时代的前端考点
  • 历年面经NEW按年份追踪真实考点
  • 每日一题每天一道,攒手感
  • 专项自测100 题快速查漏
  • 小程序题库小程序专项刷题
  • 算法题库NEW在线编码即时判题
  • 知识卡片NEW碎片时间过考点
  • 面试题大全常见问题解析
  • AI 答疑NEW随时提问,即时解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • AI 定制路线NEW按你的简历现排
  • AI 知识地图NEW串起全站知识点
  • 原理篇React / Vue 源码拆解
  • HTTP从报文一路讲到 HTTPS
  • 浏览器渲染、事件循环、进程
  • 计算机基础Linux、网络、操作系统
  • 设计模式23 种模式怎么用
  • Node学习指南从环境搭建到服务端
  • NPM工作流script、依赖与发布
  • Docker容器化部署上手
  • Canvas图形与动画实战
  • 前端系统进阶学习大型项目工程化
  • 前端综合文章长期沉淀的实践文
  • 思维导图知识点全景图
  • 学习路线按图索骥不跑偏
  • AI 热点NEWAI 每日动态
  • 公众号动态公众号历史文章
  • 博客动态站长的技术博客
  • 开发者导航常用工具与文档站
  • 基础篇HTML/CSS/JS 打底
  • 进阶篇原理与工程化
  • 高频篇面试最常问的那批
  • 精选篇按模块收敛的总结
  • 手写篇常考代码手写实现
  • 面经篇真实面试问题复盘
  • AI 篇NEWAI 时代的前端考点
  • 历年面经NEW按年份追踪真实考点
  • 每日一题每天一道,攒手感
  • 专项自测100 题快速查漏
  • 小程序题库小程序专项刷题
  • 算法题库NEW在线编码即时判题
  • 知识卡片NEW碎片时间过考点
  • 面试题大全常见问题解析
  • AI 答疑NEW随时提问,即时解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • AI 定制路线NEW按你的简历现排
  • AI 知识地图NEW串起全站知识点
  • 原理篇React / Vue 源码拆解
  • HTTP从报文一路讲到 HTTPS
  • 浏览器渲染、事件循环、进程
  • 计算机基础Linux、网络、操作系统
  • 设计模式23 种模式怎么用
  • Node学习指南从环境搭建到服务端
  • NPM工作流script、依赖与发布
  • Docker容器化部署上手
  • Canvas图形与动画实战
  • 前端系统进阶学习大型项目工程化
  • 前端综合文章长期沉淀的实践文
  • 思维导图知识点全景图
  • 学习路线按图索骥不跑偏
  • AI 热点NEWAI 每日动态
  • 公众号动态公众号历史文章
  • 博客动态站长的技术博客
  • 开发者导航常用工具与文档站
首页程序员面试题库GraphQL custom scalar DateTime 序列化
APAPI 设计GraphQL

GraphQL 中如何定义和使用自定义标量类型(如 DateTime)?

自定义标量需在 schema 声明 scalar DateTime,并实现 serialize、parseValue、parseLiteral 三个函数,分别处理输出、变量输入和查询字面量输入。

前端进阶之旅 · 一题精讲更新于 2026.09.05
API 设计#GraphQL
先看核心答案
理解线索

标量转换的三重职责

  1. serialize内部值转 JSON(输出)
  2. parseValue变量 JSON 值转内部(输入)
  3. parseLiteral查询字面量转内部(输入)

输入输出格式需客户端与服务端共享约定,否则会静默出错。

核心回答

先记住这个答案

自定义标量通过声明 scalar 类型并定义三个解析函数来扩展 GraphQL 的基础类型。serialize 用于将服务端内部值转换为 JSON 可传输格式(输出);parseValue 用于将变量中的 JSON 值转换为内部表示(输入);parseLiteral 则解析查询字符串中的字面量。例如 DateTime 通常序列化为 ISO 8601 字符串或时间戳。

  • 自定义标量需实现三个函数
  • serialize 用于输出,输入用 parseValue/parseLiteral
  • DateTime 内部存储不一定是字符串

三个转换函数的职责与差异

GraphQL 默认提供 Int、Float、String、Boolean、ID 五种标量,但业务中常需要日期时间、URL 等精确类型。自定义标量在 SDL 中用 scalar DateTime 声明,实际行为由服务端实现决定。它要求提供三个函数。 serialize(value) 在字段作为输出时调用,将服务端内部值(如 Date 对象或时间戳)转换成客户端可接收的 JSON 兼容值。 parseValue(value) 在字段作为参数且值来自查询的 variables 对象时调用,将变量中的 JSON 值转换为内部表示,用于后续解析或处理。 parseLiteral(ast) 在字段作为参数且值直接写在查询字符串中时调用,接收的是抽象语法树(AST)节点,通常是 StringValueNode 或 IntValueNode,需从节点中提取字面量再转换。输入路径有两个函数是因为变量和字面量的来源不同,parseValue 接收已解析的 JSON,parseLiteral 直接面对 AST。若未实现 parseLiteral,那么查询中直接用字面量(如 since: "2024-01-01T00:00:00Z")将无法工作。

这三者的返回值和错误处理也有区别。 serialize 可返回字符串、数字或布尔值,这些值最终会被写入响应 JSON;如果返回 Date 对象会被错误序列化,导致客户端收到格式混乱。 parseValue 和 parseLiteral 的返回值将作为 resolver 的参数,其类型应当与内部处理所需一致。若输入无效,应抛出 GraphQLError 或自定义错误,GraphQL 执行引擎会将其加入 errors,且该字段将得到 null。注意 schema 上的 scalar DateTime 定义并不强制校验输入是字符串还是数字,因此服务端必须自行决定一种规范格式。

事件查询场景中 DateTime 的实现

假设有一个活动排期系统,客户端需要查询“在 2024-03-01 之后发布的文章”和创建文章的 mutation。后端内部存储时间统一使用 Unix 时间戳(毫秒)。服务端使用 Apollo Server 与 graphql-tools。Schema 中定义 scalar DateTime,字段 publishedAt: DateTime,输入 filter: { since: DateTime }。实现时,serialize(timestampMs) 返回 new Date(timestampMs).toISOString(),输出为 ISO 8601 字符串;parseValue(value) 接收来自变量的字符串(如 "2024-03-01T00:00:00Z"),校验格式并返回 Date.parse(value);parseLiteral(ast) 检查 ast.kind === Kind.STRING,从 ast.value 中提取字符串并同样调用 Date.parse。这样,客户端可以使用字符串变量获得统一输入,而输出始终是带时区标记的字符串,便于前端直接显示。

为什么内部不直接存字符串?因为 ISO 字符串在比较时依赖时区且不可直接计算,而 Unix 毫秒数值让数据库索引和比较更高效。序列化时再转成字符串控制格式。实际执行时,resolver 从数据库取出的 timestamp 传给 serialize,返回 ISO 字符串;输入时 parseValue 会把合法日期字符串解析为毫秒数,resolver 再利用这个毫秒数构造查询条件。若客户端传入格式如 "2024/03/01",实现应抛出“无效日期格式”错误,让客户端调整,而不能静默转为 null 或使用默认值。

自定义标量的限制与成本

自定义标量只在单个 schema 内部统一,跨服务(如联邦服务)时需在每个服务中重复定义实现,且客户端也依赖约定。若内部保存的就是 ISO 字符串,仍然可以自定义标量,但 serialize 需做正则验证,parseValue 和 parseLiteral 需校验并可能转换为标准对象。但必须明确的是,GraphQL 规范不要求标量输出与输入必须使用同一种表示。例如输出可以是时间戳,输入要求 ISO 字符串,这会让客户端不一致,因此强烈建议定义成一种对客户端友好的格式(如 ISO 8601),并在文档中指明。

实现上的易错点包括:parseLiteral 中忽略 IntValueNode 导致数字输入失败;serialize 返回了非 JSON 兼容值(如 Date 对象)或未处理格式异常。当字段值为 null 时,序列化过程不会调用 serialize,而是由字段可空性处理;若内部值存在,serialize 必须返回正确的标量类型,否则会抛错。规范并不要求 serialize 返回 null 来标记错误,错误应明确抛出。

回答前,多想一步

容易答错的地方

以为必须内部存字符串
常见误区是自定义 DateTime 标量就必须用字符串存储,实际上 serialize 是转换层,内部可以存时间戳、Date 对象或其它业务结构。关键是三个函数负责双向转换,只要输入输出合法,内部存储随意。
忽略 parseLiteral 导致查询失败
有些实现只写了 parseValue 忽略 parseLiteral,导致使用字面量参数时直接报错或返回 null。应在实现中同时覆盖 ParseValue 和 ParseLiteral,确保内联字面量和 variables 都支持。
试着用自己的话回答

面试官还会怎么问?

serialize 返回 null 是否正确?

如果字段解析结果为 null,且字段是 nullable,则不会调用 serialize,直接响应 null。如果内部值存在,serialize 必须返回正确格式的值;若 serialize 返回 null,对于 nullable 字段会得到 null,对于非空字段会引发父级错误,所以不应返回 null 来表示序列化失败。规范并不禁止 serialize 返回 null,但 null 应视为字段可空,而非错误标记。若内部值存在但不能转为正确格式,应明确抛错,而不是返回 null 或掩盖错误。

parseValue 和 parseLiteral 抛错后查询结果是什么?

抛错后该字段的值为 null,错误对象被加入 errors 数组,不影响其他字段。如果字段非空,错误会冒泡至可空父级。建议错误信息包含预期格式示例,便于客户端修正。

如何选择 DateTime 的输出格式?字符串还是数字?

常用的是 ISO 8601 字符串,因为人类可读且自带时区。时间戳数字节省空间但可读性差,且需约定单位(秒/毫秒)。若API已有客户端影响,可选用 GraphQL 社区日历库默认的 ISO 字符串或 RFC3339,但必须一致并明确文档。

从一道题,走向一组知识

把知识连起来

GraphQL

GraphQL 的 Interface 与 Union 类型在语义和使用场景上有什么区别?

同属「GraphQL」专题,接着看 GraphQL interface union 区别 在具体场景中的处理方式。

GraphQL

GraphQL 如何在不破坏现有客户端的前提下废弃字段?

同属「GraphQL」专题,接着看 GraphQL deprecated 字段 演进 在具体场景中的处理方式。

参考资料

  • Schemas and Types

示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。

本题目录
  1. 先记住这个答案
  2. 三个转换函数的职责与差异
  3. 事件查询场景中 DateTime 的实现
  4. 自定义标量的限制与成本
  5. 容易答错的地方
  6. 面试官还会怎么问
  7. 把知识连起来
读懂,再试着讲出来

先看核心答案,再读代码。最后展开追问,检查自己有没有遗漏边界。

试着回答追问
浏览全部面试题理解原理,也关注真实的使用场景。回到顶部 ↑