Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

GraphQL ObjectType with dynamic fields based on arguments

Tags:

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.

like image 512
TJR Avatar asked Aug 23 '17 14:08

TJR


People also ask

Can we provide arguments to every field and nested objects in GraphQL?

But in GraphQL, every field and nested object can get its own set of arguments, making GraphQL a complete replacement for making multiple API fetches. You can even pass arguments into scalar fields, to implement data transformations once on the server, instead of on every client separately.

What are the three types of operations in GraphQL?

GraphQL works by sending operations to an endpoint. There are three types of operations: queries, mutations, and subscriptions.

What is __ Typename in GraphQL?

The __typename field returns the object type's name as a String (e.g., Book or Author ). GraphQL clients use an object's __typename for many purposes, such as to determine which type was returned by a field that can return multiple types (i.e., a union or interface).


1 Answers

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.

like image 99
Daniel Rearden Avatar answered Oct 12 '22 22:10

Daniel Rearden