GraphQL自定义ArbitraryObject标量执行创建更新类Mutation报错解决方案
问题核心原因
报错本质是两方面的配置错位:
- 自定义标量
ArbitraryObject的实现不符合GraphQL标量的解析规则,不支持直接接收对象类型的入参 - Mutation操作中,给类型为标量的字段传了嵌套对象字面量,触发GraphQL强制校验规则拦截
GraphQL对所有标量类型(无论内置还是自定义)有统一校验逻辑:标量是没有子字段的叶节点,一旦检测到给标量字段写了大括号包裹的选择集,就会直接抛出GRAPHQL_VALIDATION_FAILED错误。
具体代码缺陷
- 自定义标量的AST解析逻辑错误:
当前parseLiteral只支持解析字符串类型的AST节点,遇到客户端传入的原生对象(对应AST节点类型为Kind.OBJECT)直接抛错,根本无法处理对象类型的入参。 - 自定义标量的变量解析逻辑反向操作:
parseValue函数的入参是客户端通过GraphQL变量传入的、已经完成JSON反序列化的原生JS值,当前实现反而把这个值又序列化成了字符串,导致输入数据被不必要转码。 - 自定义标量的注册方式错误:
定义ObjectScalarType时把它写成了返回实例的工厂函数,resolvers里注册时也没有实际生成标量实例,导致Schema构建时没有正确加载ArbitraryObject的解析规则。 - 序列化逻辑的类型判断错误:
serialize函数里判断value === 'object'的代码永远不会为真,JS中判断对象类型应该使用typeof value === 'object'。
修复步骤
- 修正自定义标量的完整实现,使其支持原生对象的解析、序列化,替换原来的
ObjectScalarType定义:const ArbitraryObjectScalar = new GraphQLScalarType({ name: 'ArbitraryObject', description: '支持任意键结构的对象类型标量', // 处理客户端通过变量传入的原生JS值,直接返回即可 parseValue: (value) => value, // 处理返回给客户端的data,结合grpc解码的前置逻辑,兼容对象和字符串两种输入 serialize: (value) => { if (typeof value === 'object' && value !== null) { return value } try { return JSON.parse(value) } catch (e) { log.contextLogger(e).error(e, "Failed to serialize arbitrary object") return null } }, // 递归遍历解析操作语句中直接书写的对象字面量 parseLiteral: (ast, variables) => { // 支持解析对象类型的AST节点 if (ast.kind === Kind.OBJECT) { return ast.fields.reduce((obj, field) => { obj[field.name.value] = ArbitraryObjectScalar.parseLiteral(field.value, variables) return obj }, {}) } // 兼容原有的传字符串的场景 if (ast.kind === Kind.STRING) { try { return JSON.parse(ast.value) } catch (e) { return ast.value } } // 支持数组、数字、布尔等基础类型嵌套 if ([Kind.INT, Kind.FLOAT, Kind.BOOLEAN].includes(ast.kind)) { return Number(ast.value) } if (ast.kind === Kind.LIST) { return ast.values.map(item => ArbitraryObjectScalar.parseLiteral(item, variables)) } return null } }) - 修正resolvers中的标量注册逻辑,直接把上面生成的标量实例注册进去,不要套工厂函数:
export const resolvers = { // 保留原有Query、Mutation resolver逻辑不变 ArbitraryObject: ArbitraryObjectScalar, } - 调整Mutation传参逻辑:
修复后的标量已经支持解析对象字面量,不会再触发校验;如果通过GraphQL变量传值,直接把原生JS对象作为变量值传入即可,不需要手动做字符串序列化。 - 保留原有的grpc编解码逻辑:
getFormattedIngest函数在Mutation场景下把原生JS对象通过struct.encode转成grpc要求的Struct结构的逻辑是正确的,只需要避免在标量解析阶段重复做转码即可。
注意:自定义标量的核心作用就是把任意复杂的结构在GraphQL边界层做透传,不需要手动做JSON序列化、字符串转码,只要把解析和序列化逻辑写对,就可以原生支持对象类型的入参和出参。
内容的提问来源于stack exchange,提问作者chikipiki_3443
相关产品推荐
相关产品推荐

