Flowが「any」タイプを持つ方法と同様に、GraphQLのフィールドをブラックボックスに指定することは可能ですか?スキーマには、String、Boolean、Object、Arrayなどの任意の値を受け入れることができるフィールドがあります。
@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!
}
私は中間的な解決策を思いつきました。この複雑さをGraphQLにプッシュしようとするのではなく、String
タイプとJSON.stringify
フィールドに設定する前にデータを入力します。したがって、すべてが文字列化され、後でこのフィールドを使用する必要があるアプリケーションで、JSON.parse
目的のオブジェクト/配列/ブール値/などを取得する結果.
はい。何でも許可する新しい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;
}
}
ほとんどのユースケースでは、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}`)
}
}
スカラーはoutputs(応答で返される場合)およびinputs(フィールド引数の値として使用される場合)の両方として使用されることに注意してください)。 serialize
メソッドは、リゾルバーで返された値を応答で返されたdata
にserializeする方法をGraphQLに指示します。 parseLiteral
メソッドは、GraphQLに、引数に渡されたリテラル値("foo"
、または4.2
または[12, 20]
)。 parseValue
メソッドは、引数に渡されたvariableの値をどう処理するかをGraphQLに指示します。
parseValue
およびserialize
の場合、指定された値を返すことができます。 parseLiteral
には、リテラル値を表すASTノードオブジェクトが指定されているため、適切な形式に変換するには少し作業が必要です。
上記のスカラーを取得し、必要に応じて検証ロジックを追加することにより、ニーズに合わせてカスタマイズできます。 3つの方法のいずれでも、無効な値を示すエラーをスローできます。たとえば、ほとんどの値を許可したいが、関数をシリアル化したくない場合、次のようなことができます。
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
`