Можно ли указать, что поле в GraphQL должно быть черным ящиком, аналогичным тому, как Flow имеет "любой" тип? У меня есть поле в моей схеме, которое должно принимать любое произвольное значение, которое может быть String, Boolean, Object, Array и т.д.
GraphQL Blackbox/ "Любой" тип?
Ответ 1
Ответ на@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!
}
Ответ 2
Я придумал решение средней линии. Вместо того, чтобы пытаться выполнить эту сложность на GraphQL, я предпочитаю использовать только теги String
и JSON.stringify
для моих данных, прежде чем устанавливать их в поле. Итак, все становится строгим, а позже в моем приложении, когда мне нужно использовать это поле, я JSON.parse
результат, чтобы вернуть желаемый объект/array/boolean/etc.
Ответ 3
Да. Просто создайте новый 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;
}
}
Ответ 4
В большинстве случаев вы можете использовать скалярный тип 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}')
}
}
Обратите внимание, что скаляры используются как в качестве выходных данных (когда возвращаются в вашем ответе), так и в качестве входных данных (когда используются в качестве значений для аргументов поля). Метод 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
Использовать скаляр выше в вашей схеме просто. Если вы используете vanilla 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
'