Javascript GraphQL黑盒/quot;任何;类型?
是否可以指定GraphQL中的字段应该是黑盒,类似于流的“any”类型?我的模式中有一个字段,可以接受任意值,可以是字符串、布尔值、对象、数组等。是。只需创建一个新的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';
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!,其他文件…)