简体   繁体   English

GraphQL Blackbox / “任何”类型?

[英]GraphQL Blackbox / “Any” type?

Is it possible to specify that a field in GraphQL should be a blackbox, similar to how Flow has an "any" type?是否可以指定 GraphQL 中的字段应该是黑盒,类似于 Flow 具有“任何”类型的方式? I have a field in my schema that should be able to accept any arbitrary value, which could be a String, Boolean, Object, Array, etc.我的架构中有一个字段应该能够接受任何任意值,可以是字符串、布尔值、对象、数组等。

I've come up with a middle-ground solution.我想出了一个折衷的解决方案。 Rather than trying to push this complexity onto GraphQL, I'm opting to just use the String type and JSON.stringify ing my data before setting it on the field.我没有尝试将这种复杂性推到 GraphQL 上,而是选择只使用String类型和JSON.stringify我的数据设置在字段上之前。 So everything gets stringified, and later in my application when I need to consume this field, I JSON.parse the result to get back the desired object/array/boolean/ etc.所以一切都被字符串化,然后在我的应用程序中,当我需要使用这个字段时,我JSON.parse结果以取回所需的对象/数组/布尔值/等。

@mpen's answer is great, but I opted for a more compact solution: @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
    }
  }
})

Then my resolvers looks like:然后我的解析器看起来像:

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

And my .gql looks like:我的.gql看起来像:

scalar Object

type Foo {
  id: ID!
  values: Object!
}

Yes.是的。 Just create a new GraphQLScalarType that allows anything.只需创建一个允许任何内容的新GraphQLScalarType

Here's one I wrote that allows objects.这是我写的一个允许对象。 You can extend it a bit to allow more root types.您可以稍微扩展它以允许更多的根类型。

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;
    }
}

For most use cases, you can use a JSON scalar type to achieve this sort of functionality.对于大多数用例,您可以使用 JSON 标量类型来实现此类功能。 There's a number of existing libraries you can just import rather than writing your own scalar -- for example, graphql-type-json .您可以直接导入许多现有的库,而无需编写自己的标量——例如, graphql-type-json

If you need a more fine-tuned approach, than you'll want to write your own scalar type.如果您需要更精细的方法,那么您将需要编写自己的标量类型。 Here's a simple example that you can start with:这是一个简单的示例,您可以从以下示例开始:

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}`)
  }
}

Note that scalars are used both as outputs (when returned in your response) and as inputs (when used as values for field arguments).请注意,标量既用作输出(在响应中返回时)又用作输入(用作字段参数的值时)。 The serialize method tells GraphQL how to serialize a value returned in a resolver into the data that's returned in the response. serialize方法告诉 GraphQL 如何将解析器中返回的值序列化为响应中返回的data The parseLiteral method tells GraphQL what to do with a literal value that's passed to an argument (like "foo" , or 4.2 or [12, 20] ). parseLiteral方法告诉 GraphQL 如何处理传递给参数的文字值(如"foo" ,或4.2[12, 20] )。 The parseValue method tells GraphQL what to do with the value of a variable that's passed to an argument. parseValue方法告诉 GraphQL 如何处理传递给参数的变量值。

For parseValue and serialize we can just return the value we're given.对于parseValueserialize我们可以只返回给定的值。 Because parseLiteral is given an AST node object representing the literal value, we have to do a little bit of work to convert it into the appropriate format.因为parseLiteral被赋予一个代表字面值的 AST 节点对象,所以我们必须做一些工作来将其转换为适当的格式。

You can take the above scalar and customize it to your needs by adding validation logic as needed.您可以通过根据需要添加验证逻辑来​​获取上述标量并根据您的需要对其进行自定义。 In any of the three methods, you can throw an error to indicate an invalid value.在这三种方法中的任何一种中,您都可以抛出错误以指示无效值。 For example, if we want to allow most values but don't want to serialize functions, we can do something like:例如,如果我们想允许大多数值但不想序列化函数,我们可以这样做:

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

Using the above scalar in your schema is simple.在您的架构中使用上述标量很简单。 If you're using vanilla GraphQL.js, then use it just like you would any of the other scalar types ( GraphQLString , GraphQLInt , etc.) If you're using Apollo, you'll need to include the scalar in your resolver map as well as in your SDL:如果您使用的是 vanilla 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
`

Just send a stringified value via GraphQL and parse it on the other side, eg use this wrapper class.只需通过 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);
    }
}

For similar problem I've created schema like this:对于类似的问题,我创建了这样的架构:

"""`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
}

And my queries can look like this:我的查询可能如下所示:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM