Warm tip: This article is reproduced from stackoverflow.com, please click
graphql graphql-js

graphql, union scalar type?

发布于 2020-03-27 10:24:16

The payload field can be Int or String scalar type. when I write it like union type:

const schema = `
  input QuickReply {
    content_type: String
    title: String
    payload: Int | String
    image_url: String
  }
`

I got an error:

GraphQLError: Syntax Error GraphQL request (45:18) Expected Name, found |

    44:     title: String
    45:     payload: Int | String
                         ^
    46:     image_url: String

It seems GraphQL does not support union scalar type.

So, how can I solve this situation?

Questioner
slideshowp2
Viewed
65
Daniel Rearden 2020-03-15 19:47

Scalars can't be used as part of unions, since per the specification, unions specifically "represent an object that could be one of a list of GraphQL Object types." Instead, you can use a custom scalar. For example:

const MAX_INT = 2147483647
const MIN_INT = -2147483648
const coerceIntString = (value) => {
  if (Array.isArray(value)) {
    throw new TypeError(`IntString cannot represent an array value: [${String(value)}]`)
  }
  if (Number.isInteger(value)) {
    if (value < MIN_INT || value > MAX_INT) {
      throw new TypeError(`Value is integer but outside of valid range for 32-bit signed integer: ${String(value)}`)
    }
    return value
  }
  return String(value)
}
const IntString = new GraphQLScalarType({
  name: 'IntString',
  serialize: coerceIntString,
  parseValue: coerceIntString,
  parseLiteral(ast) {
    if (ast.kind === Kind.INT) {
      return coerceIntString(parseInt(ast.value, 10))
    }
    if (ast.kind === Kind.STRING) {
      return ast.value
    }
    return undefined
  }
})

This code effectively combines the behaviors for both the Int and String types, while still enforcing the range for 32-bit signed integers. However, you could have whatever type coercion behavior you want. Check out the source code to see how the built-in scalars work, or this article for more details around how custom scalars work.

Note that if you're trying to return one of several scalars for an output field, it's possible to utilize a union for the parent type to achieve a similar result. For example, this isn't possible:

type Post {
  content: String | Int
}

but you can do the following:

type PostString {
  content: String
}

type PostInt {
  content: Int
}

union Post = PostString | PostInt