GraphQL 黑盒/“任何”类型?

IT技术 javascript graphql graphql-js
2021-01-26 10:20:14

是否可以指定 GraphQL 中的字段应该是黑盒,类似于 Flow 具有“任何”类型的方式?我的架构中有一个字段应该能够接受任何任意值,可以是字符串、布尔值、对象、数组等。

6个回答

我想出了一个折衷的解决方案。我没有尝试将这种复杂性推到 GraphQL 上,而是选择只使用String类型并JSON.stringify在将数据设置到字段上之前对其进行 ing。所以一切都被字符串化,然后在我的应用程序中,当我需要使用这个字段时,我JSON.parse的结果是取回所需的对象/数组/布尔值/等。

聪明的。感谢分享这个。
2021-03-18 10:20:14
谢谢 :) 这不是一个完美的解决方案,但肯定是一个务实的解决方案
2021-03-23 10:20:14
这是辉煌而简单的,喜欢它。
2021-04-02 10:20:14
聪明的做法,不错!
2021-04-12 10:20:14

@mpen 的回答很好,但我选择了更紧凑的解决方案:

const { GraphQLScalarType } = require('graphql')
const { Kind } = require('graphql/language')

const ObjectScalarType = new GraphQLScalarType({
  name: 'Object',
  description: 'Arbitrary object',
  parseValue: (value) => {
    return typeof value === 'object' ? value
      : typeof value === 'string' ? JSON.parse(value)
      : null
  },
  serialize: (value) => {
    return typeof value === 'object' ? value
      : typeof value === 'string' ? JSON.parse(value)
      : null
  },
  parseLiteral: (ast) => {
    switch (ast.kind) {
      case Kind.STRING: return JSON.parse(ast.value)
      case Kind.OBJECT: throw new Error(`Not sure what to do with OBJECT for ObjectScalarType`)
      default: return null
    }
  }
})

然后我的解析器看起来像:

{
  Object: ObjectScalarType,
  RootQuery: ...
  RootMutation: ...
}

我的.gql样子:

scalar Object

type Foo {
  id: ID!
  values: Object!
}
具有 uniq id 类型的对象呢?(id:字符串!,其他文件......)
2021-03-24 10:20:14

是的。只需创建一个GraphQLScalarType允许任何事情的新内容。

这是我写的允许对象的一个​​。您可以稍微扩展它以允许更多的根类型。

import {GraphQLScalarType} from 'graphql';
import {Kind} from 'graphql/language';
import {log} from '../debug';
import Json5 from 'json5';

export default new GraphQLScalarType({
    name: "Object",
    description: "Represents an arbitrary object.",
    parseValue: toObject,
    serialize: toObject,
    parseLiteral(ast) {
        switch(ast.kind) {
            case Kind.STRING:
                return ast.value.charAt(0) === '{' ? Json5.parse(ast.value) : null;
            case Kind.OBJECT:
                return parseObject(ast);
        }
        return null;
    }
});

function toObject(value) {
    if(typeof value === 'object') {
        return value;
    }
    if(typeof value === 'string' && value.charAt(0) === '{') {
        return Json5.parse(value);
    }
    return null;
}

function parseObject(ast) {
    const value = Object.create(null);
    ast.fields.forEach((field) => {
        value[field.name.value] = parseAst(field.value);
    });
    return value;
}

function parseAst(ast) {
    switch (ast.kind) {
        case Kind.STRING:
        case Kind.BOOLEAN:
            return ast.value;
        case Kind.INT:
        case Kind.FLOAT:
            return parseFloat(ast.value);
        case Kind.OBJECT: 
            return parseObject(ast);
        case Kind.LIST:
            return ast.values.map(parseAst);
        default:
            return null;
    }
}

对于大多数用例,您可以使用 JSON 标量类型来实现此类功能。您可以直接导入许多现有的库,而无需编写自己的标量——例如,graphql-type-json

如果您需要更精细的方法,那么您将需要编写自己的标量类型。这是一个简单的示例,您可以从以下示例开始:

const { GraphQLScalarType, Kind } = require('graphql')
const Anything = new GraphQLScalarType({
  name: 'Anything',
  description: 'Any value.',
  parseValue: (value) => value,
  parseLiteral,
  serialize: (value) => value,
})

function parseLiteral (ast) {
  switch (ast.kind) {
    case Kind.BOOLEAN:
    case Kind.STRING:  
      return ast.value
    case Kind.INT:
    case Kind.FLOAT:
      return Number(ast.value)
    case Kind.LIST:
      return ast.values.map(parseLiteral)
    case Kind.OBJECT:
      return ast.fields.reduce((accumulator, field) => {
        accumulator[field.name.value] = parseLiteral(field.value)
        return accumulator
      }, {})
    case Kind.NULL:
        return null
    default:
      throw new Error(`Unexpected kind in parseLiteral: ${ast.kind}`)
  }
}

请注意,标量既用作输出(在响应中返回时)又用作输入(用作字段参数的值时)。serialize方法告诉 GraphQL 如何将解析器中返回的值序列化为data响应中返回的值。parseLiteral方法告诉 GraphQL 如何处理传递给参数(如"foo", or4.2[12, 20]的文字值parseValue方法告诉 GraphQL 如何处理传递给参数变量

For parseValueandserialize我们可以只返回给定的值。因为parseLiteral给定了一个代表字面值的 AST 节点对象,我们必须做一些工作才能将其转换为适当的格式。

您可以通过根据需要添加验证逻辑来​​获取上述标量并根据您的需要对其进行自定义。在这三种方法中的任何一种中,您都可以抛出错误以指示无效值。例如,如果我们想允许大多数值但不想序列化函数,我们可以这样做:

if (typeof value == 'function') {
  throw new TypeError('Cannot serialize a function!')
}
return value

在您的架构中使用上述标量很简单。如果您使用的香草GraphQL.js,然后使用它,就像你将任何其他标量类型(的GraphQLStringGraphQLInt等等),如果你使用的阿波罗,你需要包括标在您的解析器地图以及就像在您的 SDL 中一样:

const resolvers = {
  ...
  // The property name here must match the name you specified in the constructor
  Anything,
}

const typeDefs = `
  # NOTE: The name here must match the name you specified in the constructor
  scalar Anything

  # the rest of your schema
`

只需通过 GraphQL 发送一个字符串化的值并在另一端解析它,例如使用这个包装类。

export class Dynamic {

    @Field(type => String)
    private value: string;

    getValue(): any {
        return JSON.parse(this.value);
    }

    setValue(value: any) {
        this.value = JSON.stringify(value);
    }
}