是否可以指定GraphQL中的字段应该是黑盒子,类似于Flow具有“任何”类型的方式?我的架构中有一个字段应该能够接受任意值,可以是String,Boolean,Object,Array等。
答案 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处理传递给参数的变量的值。
对于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
`
答案 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
}
}
}