Graphql-js: What is proper way to convert GraphQLObjectType to GraphQLInputObjectType?

Created on 13 Mar 2016  路  6Comments  路  Source: graphql/graphql-js

I have RefType and it's widely using in other types:

import {
  GraphQLObjectType,
  GraphQLString,
} from 'graphql';

const RefType = new GraphQLObjectType({
  name: 'Ref',
  fields: {
    i: { type: GraphQLString, description: 'ObjectId as string' },
    t: { type: GraphQLString, description: 'ORM-model class name' },
  },
});

export default RefType;

Today I want to pass this type like input field for mutation:

const NotesAddMutation = mutationWithClientMutationId({
  name: 'NotesAdd',
  inputFields: {
    ref: { type: RefType },
    text: { type: GraphQLString },
  },
  outputFields: {
...

And get error NotesAddInput.ref field type must be Input Type but got: Ref. A couple minutes of googling gets answer to use GraphQLInputObjectType.

So I have several questions:
1) Does GQ have some object type for my RefType, which I can use for queries and mutations? My RefType is like scalar type, but have two values. Analogous type may be LatitudeLongitudeType as example.
2) What is proper way convert GraphQLObjectType to GraphQLInputObjectType? Or I should define two types (one type for queries, second for mutations and may be third in future, when subscriptions come)?
3) May be error message can be improved, because right now it does not help to determine what is wrong?

PS. If somebody tries to find understanding of a design purpose GraphQLObjectType and GraphQLInputObjectType, you may read https://medium.com/@HurricaneJames/graphql-mutations-fb3ad5ae73c4#.wi9iwkgot

Most helpful comment

@johanatan you may use this ready solution:

import { TypeComposer } from 'graphql-compose';

const yourConvertedInputType = TypeComposer.create(YourOutputType).getInputType();

All 6 comments

I'll answer 1 and 3 first.

InputObjectType can only have other input types as it's members. This is because ObjectType might have mutually-recursive dependencies with other types, which can't be resolved to a JSON structure. Thus you can't use Ref directly in InputObjects. Error is just telling you that - ObjectType is not a kind of type you can use as input and thus as part of input object.

Now for question number 2. There are multiple approaches to how to handle this, in some APIs, you might design all your inputs to be custom and thus never convert from object types. Otherwise we've followed the following approach at Reindex:

  • Go through all the fields of the ObjectType (through type.getFields())
  • If field is a scalar, add it to InputObjectType as is
  • If field is a ObjectType

    • if it can be referenced by an id (in our case, we check if ObjectType has Relay's Node interface), then add reference to it (id) to InputObject

    • if it is "inline" type, recursively call the conversion function on it, get InputObject and add it

  • If field is GraphQLNonNull or GraphQLList, inspect the inner type as above, and add result wrapped in corresponding wrapper

One thing to note - non-nullity has a bit different meaning in GraphQLObject and GraphQLInputObject. For input object it means that the field is required, for objects that means that not returning a non-null value for that field is an error. You might, in some cases, have non-null in InputObject, but not in an Object, because null is often a valid return value in GraphQL, eg when permissions are invalid.

Here is example code that implements the above algorithm:

import { mapValues } from 'lodash';
import {
  GraphQLNonNull,
  GraphQLScalarType,
  GraphQLInputObjectType,
  GraphQLEnumType,
} from 'graphql';

export default function createInputObject(
  type
) {
  return new GraphQLInputObjectType({
     name: type.name + 'Input', 
     fields: mapValues(type.getFields(), (field) =>
       convertInputObjectField(field)
     ),
   });
}

function convertInputObjectField(
  field,
) {
  let fieldType = field.type;
  const wrappers = [];

  while (fieldType.ofType) {
    wrappers.unshift(fieldType.constructor);
    fieldType = fieldType.ofType;
  }

  if (!(fieldType instanceof GraphQLInputObjectType ||
        fieldType instanceof GraphQLScalarType ||
        fieldType instanceof GraphQLEnumType)) {
    fieldType = fieldType.getInterfaces().includes(NodeInterface) ?
      ID :
      createInputObject(fieldType)
  }

  fieldType = wrappers.reduce((type, Wrapper) => {
    return new Wrapper(type);
  }, fieldType);

  return { type: fieldType };
}

@freiksenet
YAY, many thanks!!!
Definitely, +100 to karma for next brewing ;)

@freiksenet Can you explain where NodeInterface is defined? I scanned the entire GraphQL source (0.8.1) and can't find it.

Also, did you mean GraphQLID rather than ID? If not, where is ID defined?

@johanatan you may use this ready solution:

import { TypeComposer } from 'graphql-compose';

const yourConvertedInputType = TypeComposer.create(YourOutputType).getInputType();
Was this page helpful?
0 / 5 - 0 ratings