GraphQL Blackbox /“任何”类型?

时间:2017-08-09 19:06:57

标签: javascript graphql graphql-js

是否可以指定GraphQL中的字段应该是黑盒子,类似于Flow具有“任何”类型的方式?我的架构中有一个字段应该能够接受任意值,可以是String,Boolean,Object,Array等。

6 个答案:

答案 0 :(得分:10)

@ 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!
}

答案 1 :(得分:9)

我提出了一个中间解决方案。我没有尝试将这种复杂性推广到GraphQL上,而是选择在{+ 1}}类型和String之前使用我的数据,然后再将其设置在字段上。所以一切都得到了字符串化,后来在我需要使用这个字段的应用程序中,我JSON.stringify得到了所需的对象/数组/布尔/等的结果。

答案 2 :(得分:6)

是。只需创建一个允许任何内容的新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;
    }
}

答案 3 :(得分:3)

只需通过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);
    }
}

答案 4 :(得分:0)

在大多数情况下,您可以使用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}`)
  }
}

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

对于parseValueserialize,我们可以返回我们得到的值。因为parseLiteral被赋予了代表文字值的AST节点对象,所以我们需要做一些工作才能将其转换为适当的格式。

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

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

在架构中使用上述标量很简单。如果您使用香草GraphQL.js,则就像使用其他标量类型(GraphQLStringGraphQLInt等)一样使用它。如果您使用的是Apollo,则需要将标量包含在解析器映射以及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
`

答案 5 :(得分:0)

对于类似的问题,我创建了这样的架构:

"""`MetadataEntry` model"""
type MetadataEntry {
  """Key of the entry"""
  key: String!

  """Value of the entry"""
  value: String!
}

"""Object with metadata"""
type MyObjectWithMetadata {

  """
  ... rest of my object fields
  """

  """
  Key-value entries that you can attach to an object. This can be useful for
  storing additional information about the object in a structured format
  """
  metadata: [MetadataEntry!]!

  """Returns value of `MetadataEntry` for given key if it exists"""
  metadataValue(
    """`MetadataEntry` key"""
    key: String!
  ): String
}

我的查询如下所示:

query {
  listMyObjects {
    # fetch meta values by key
    meta1Value: metadataValue(key: "meta1")
    meta2Value: metadataValue(key: "meta2")
    # ... or list them all
    metadata {
      key
      value
    }
  }
}