web-dev-qa-db-ja.com

GraphQL Blackbox /「任意」タイプ?

Flowが「any」タイプを持つ方法と同様に、GraphQLのフィールドをブラックボックスに指定することは可能ですか?スキーマには、String、Boolean、Object、Arrayなどの任意の値を受け入れることができるフィールドがあります。

19
Jon Cursi

@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!
}
12
a paid nerd

私は中間的な解決策を思いつきました。この複雑さをGraphQLにプッシュしようとするのではなく、StringタイプとJSON.stringifyフィールドに設定する前にデータを入力します。したがって、すべてが文字列化され、後でこのフィールドを使用する必要があるアプリケーションで、JSON.parse目的のオブジェクト/配列/ブール値/などを取得する結果.

11
Jon Cursi

はい。何でも許可する新しい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;
    }
}
7
mpen

ほとんどのユースケースでは、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メソッドは、リゾルバーで返された値を応答で返されたdataserializeする方法を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を使用している場合は、他のスカラータイプ(GraphQLStringGraphQLIntなど)と同じように使用します。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
`
2
Daniel Rearden