GraphQL ObjectType with dynamic fields based on ar

2020-03-09 07:19发布

We are in the situation that the response of our GraphQL Query has to return some dynamic properties of an object. In our case we are not able to predefine all possible properties - so it has to be dynamic.

As we think there are two options to solve it.

const MyType = new GraphQLObjectType({
  name: 'SomeType',
  fields: {
    name: {
      type: GraphQLString,
    },
    elements: {
      /*
      THIS is our special field which needs to return a dynamic object 
      */
    },
    // ...
  },
});

As you can see in the example code is element the property which has to return an object. A response when resolve this could be:

{
  name: 'some name',
  elements: {
    an_unkonwn_key: {
      some_nested_field: {
        some_other: true,
      },
    },
    another_unknown_prop: 'foo',
  },
}

1) Return a "Any-Object"

We could just return any object - so GraphQL do not need to know which fields the Object has. When we tell GraphQL that the field is the type GraphQlObjectType it needs to define fields. Because of this it seems not to be possible to tell GraphQL that someone is just an Object.

Fo this we have changed it like this:

elements: {
      type: new GraphQLObjectType({ name: 'elements' });
    },

2) We could define dynamic field properties because its in an function

When we define fields as an function we could define our object dynamically. But the field function would need some information (in our case information which would be passed to elements) and we would need to access them to build the field object.

Example:

const MyType = new GraphQLObjectType({
  name: 'SomeType',
  fields: {
    name: {
      type: GraphQLString,
    },
    elements: {
      type: new GraphQLObjectType({
        name: 'elements',
        fields: (argsFromElements) => {
          // here we can now access keys from "args"
          const fields = {};
          argsFromElements.keys.forEach((key) => {
            // some logic here ..
            fields[someGeneratedProperty] = someGeneratedGraphQLType;
          });
          return fields;
        },
      }),
      args: {
        keys: {
          type: new GraphQLList(GraphQLString),
        },
      },
    },
    // ...
  },
});

This could work but the question would be if there is a way to pass the args and/or resolve object to the fields.

Question So our question is now: Which way would be recommended in our case in GraphQL and is solution 1 or 2 possible ? Maybe there is another solution ?

Edit Solution 1 would work when using the ScalarType. Example:

type: new GraphQLScalarType({
        name: 'elements',
        serialize(value) {
          return value;
        },
      }),

I am not sure if this is a recommended way to solve our situation.

2条回答
看我几分像从前
2楼-- · 2020-03-09 07:47

One more possible solution could be to declare any such dynamic object as a string. And then pass a stringified version of the object as value to that object from your resolver functions. And then eventually you can parse that string to JSON again to make it again an object on the client side.

I'm not sure if its recommended way or not but I tried to make it work with this approach and it did work smoothly, so I'm sharing it here.

查看更多
家丑人穷心不美
3楼-- · 2020-03-09 07:56

Neither option is really viable:

  1. GraphQL is strongly typed. GraphQL.js doesn't support some kind of any field, and all types defined in your schema must have fields defined. If you look in the docs, fields is a required -- if you try to leave it out, you'll hit an error.

  2. Args are used to resolve queries on a per-request basis. There's no way you can pass them back to your schema. You schema is supposed to be static.

As you suggest, it's possible to accomplish what you're trying to do by rolling your own customer Scalar. I think a simpler solution would be to just use JSON -- you can import a custom scalar for it like this one. Then just have your elements field resolve to a JSON object or array containing the dynamic fields. You could also manipulate the JSON object inside the resolver based on arguments if necessary (if you wanted to limit the fields returned to a subset as defined in the args, for example).

Word of warning: The issue with utilizing JSON, or any custom scalar that includes nested data, is that you're limiting the client's flexibility in requesting what it actually needs. It also results in less helpful errors on the client side -- I'd much rather be told that the field I requested doesn't exist or returned null when I make the request than to find out later down the line the JSON blob I got didn't include a field I expected it to.

查看更多
登录 后发表回答