Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamic Input Object Types #290

Closed
bringking opened this issue Feb 10, 2016 · 7 comments
Closed

Dynamic Input Object Types #290

bringking opened this issue Feb 10, 2016 · 7 comments

Comments

@bringking
Copy link

This is a cross issue from graphql/graphql-spec#140, but I need some advice on how to handle a "dynamic" nested input type. More specifically, I am trying to model the Sequelize operators listed here - http://docs.sequelizejs.com/en/latest/docs/querying/ for my work on https://github.com/mickhansen/graphql-sequelize. Essentially, the piece I am having trouble with is the $and or $or operators. These operators can contain ...N number of nested types of themselves. e.g. -

{
  rank: {//Where type
   $gte:1, //contains operators
    $or: { //and can contain OR
      $lt: 1000, //with the same nested operators
      $eq: null
    }
  }
}

I see a PR that supports something similar, but defining the "Where" type as a unstructured JSON Blob feels like a punt.

I tried modeling this with a dynamic GraphQLInputObjectType, and using recursion -

export function operatorTypeFactory(Model, attribute, name = '') {

  // determine the base type of the column
  // use it to define the available operator queries
  let type = typeMapper.toGraphQL(Model.rawAttributes[attribute].type, Model.sequelize.constructor);

  return {
    type: new GraphQLInputObjectType({
      name: `${name}_${attribute}`,
      fields: () => {
        return {
          and: {
            type: operatorTypeFactory(Model, attribute, `${name}_AND_operator_type_${Model.name}_${attribute}`).type,
            description: 'Nested AND operator'
          },
          or: {
            type: new GraphQLList(operatorTypeFactory(Model, attribute,
              `${name}_OR_operator_type_${Model.name}_${attribute}`).type),
            description: 'Nested AND operator'
          },
          eq: {
            type: type,
            description: 'Check if value is "="'
          },

But this obviously fails with a maximum call stack error when I try and run the server.. Any thoughts?

@leebyron
Copy link
Contributor

I think you're on the right page here with using recursion. Also, I don't think it's wrong to use an unstructured JSON blob - in fact that might be a totally reasonable choice when your input is highly dynamic and difficult to type and especially so if your input type wants to be some JSON body that's not legal GraphQL input, like including $ in key names like sequalize does. Today you can do that by typing the input as a String and then just performing the json encode/decode yourself. I'm really coming around to this idea though and may propose adding this kind of dynamic untyped input as part of the formal spec in the future.

However as to your strongly typed input case: I can see why this will infinitely recurse since you're calling your type factory recursively without a termination case. However GraphQL supports recursive types, but you have to recurse by reference:

const SequalizeInput = new GraphQLInputObjectType({
  name: 'SequalizeInput',
  fields: () => ({
    and: {
      type: SequalizeInput,
      description: 'Nested AND operator'
    },
    or: {
      type: SequalizeInput,
      description: 'Nested AND operator'
    },
    eq: {
      type: SequalizeInput,
      description: 'Check if value is "="'
    }
  })
});

If you wanted to create one of these for every Model type like you're doing, then you need to create some kind of cache so you're not creating them over and over for every Model (and recursing infinitely).

export function operatorTypeFactory(Model) {
  let type = someCache.get(Model);
  if (type) {
    return type;
  }

  type = new GraphQLInputObjectType({ ... });
  someCache.set(Model, type);
  return type;
}

Hopefully this helps!

@bringking
Copy link
Author

@leebyron thanks for the helpful and thoughtful response. I ended up making a PR to graphql-sequelize using the unstructured type, so I am glad to hear you are thinking about adding something similar to the spec.

Regarding the recursion, I finally came around to the cache idea myself, so I am glad to have it validated by your idea! Thank you. I didn't end up using this solution since a side affect of the approach was quite a few very similar but different Input types in the system. Which doesn't sound like a big deal, but when you have ~ 50 model types, the documentation gets very cluttered.

@nodkz
Copy link
Contributor

nodkz commented Sep 5, 2016

For future googlers:
@taion released awesome scalar GraphQLType JSON that supports dynamic values for a field!

I'm using it in graphql-compose-mongoose, so live demos with dynamic field's type can be found here:

@granda
Copy link

granda commented Sep 15, 2016

I'm going to jump on this train and link my on going thread I'm having in the Meteor forums: https://forums.meteor.com/t/operators-in-graphql/29368.

@julian-rare
Copy link

graphql needs operators in the next implementation

@machineghost
Copy link

@leebyron

I'm really coming around to this idea though and may propose adding this kind of dynamic untyped input as part of the formal spec in the future.

Has anything happened with this since? The 3rd-party JSON GraphQLType is a good workaround but an official GraphQL type would obviously be preferable.

@IvanGoncharov
Copy link
Member

@machineghost Yes, here is PR to add Any type to the spec:
graphql/graphql-spec#325

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants