Javascript GraphQL黑盒/quot;任何;类型?

Javascript GraphQL黑盒/quot;任何;类型?,javascript,graphql,graphql-js,Javascript,Graphql,Graphql Js,是否可以指定GraphQL中的字段应该是黑盒,类似于流的“any”类型?我的模式中有一个字段,可以接受任意值,可以是字符串、布尔值、对象、数组等。是。只需创建一个新的GraphQLScalarType,它允许任何操作 这是我写的一个允许对象。您可以稍微扩展它以允许更多的根类型 import {GraphQLScalarType} from 'graphql'; import {Kind} from 'graphql/language'; import {log} from '../debug';

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

是。只需创建一个新的
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;
    }
}

我想出了一个折衷的解决办法。我没有试图将这种复杂性推到GraphQL上,而是选择只使用
String
类型和
JSON.stringify
在字段上设置数据之前对数据进行字符串化。因此,所有内容都被字符串化,稍后在我的应用程序中,当我需要使用此字段时,我会解析结果以返回所需的对象/数组/布尔值等。

@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: ...
}
scalar Object

type Foo {
  id: ID!
  values: Object!
}
我的
.gql
看起来像:

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

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

对于大多数用例,您可以使用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如何将解析器中返回的值序列化到响应中返回的
数据中。
parseLiteral
方法告诉GraphQL如何处理传递给参数的文本值(如
“foo”
,或
4.2
[12,20]
)。
parseValue
方法告诉GraphQL如何处理传递给参数的变量的值

对于
parseValue
serialize
我们可以只返回给定的值。因为
parseLiteral
被赋予了一个表示文本值的AST节点对象,所以我们需要做一些工作来将其转换为适当的格式

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

if (typeof value == 'function') {
  throw new TypeError('Cannot serialize a function!')
}
return value
在模式中使用上述标量很简单。如果您使用的是香草GraphQL.js,那么请像使用任何其他标量类型一样使用它(
GraphQLString
GraphQLInt
,等等)。如果您使用的是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
`

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

对于类似的问题,我创建了如下模式:

"""`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
    }
  }
}
我的查询可以如下所示:

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

聪明的方法,很好的一个!聪明。谢谢分享。这是一个精彩而简单的解决方案,喜欢它。谢谢:)这不是一个完美的解决方案,但肯定是一个实用的解决方案。那么一个具有uniq id类型的对象呢?(id:String!,其他文件…)