2

This is an example document of my mongoDB. I need to get the content.en array via Apollo/GraphQL. But the nested object is getting a problem for me. en is the language tag, so it would be great if this could be used as a variable.

Data in MongoDB

{
    "_id" : "9uPjYoYu58WM5Tbtf",
    "content" : {
        "en" : [
            {
                "content" : "Third paragraph",
                "timestamp" : 1484939404
            }
        ]
    },
    "main" : "Dn59y87PGhkJXpaiZ"
}

The graphQL result should be:

{
  "data": {
    "article": [
      {
        "_id": "9uPjYoYu58WM5Tbtf",
        "content": [
                {
                    "content" : "Third paragraph",
                    "timestamp" : 1484939404
                }
            ]
      }
    ]
  }
}

That means, I need to get the ID and the language specific content array.


But this is not, what I'm getting with the following setup:

Type

const ArticleType = new GraphQLObjectType({
  name: 'article',
  fields: {
    _id: { type: GraphQLID },
    content: { type: GraphQLString }
  }
})

GraphQL Schema

export default new GraphQLSchema({
  query: new GraphQLObjectType({
    name: 'RootQueryType',
    fields: {
      article: {
        type: new GraphQLList(ArticleType),
        description: 'Content of article dataset',
        args: {
          id: {
            name: 'id',
            type: new GraphQLNonNull(GraphQLID)
          }
        },
        async resolve ({ db }, args) {
          return db.collection('articles').find({ main: args.id }).toArray()
        }
      }
    }
  })
})

Query

{
  article(id: "Dn59y87PGhkJXpaiZ") {
    _id,
    content
  }
}

Result

{
  "data": {
    "article": [
      {
        "_id": "9uPjYoYu58WM5Tbtf",
        "content": "[object Object]"
      }
    ]
  }
}
user3142695
  • 15,844
  • 47
  • 176
  • 332
  • What exactly do you expect the query to return? Could you add that to your question? – DevNebulae Oct 28 '17 at 18:10
  • @DevNebulae I already posted that. See second code block. "The graphQL result should be" – user3142695 Oct 28 '17 at 18:23
  • 1
    I believe you need two args, id argument to filter articles on root resolve and language args to filter content on sub resolve. So mongo query will return all matching articles for id with all language content and graphql will return the content based on language arg. Also map the content to schema. – s7vr Oct 29 '17 at 21:26

3 Answers3

1

Since you said that the language ISO code should be a parameter and that the content is depending on a language ISO code (I'll call it languageTag from now on), I figured that you should edit your schema to look something like this:

export default new GraphQLSchema({
  query: new GraphQLObjectType({
    name: 'RootQueryType',
    fields: {
      article: {
        type: new GraphQLList(ArticleType),
        description: 'Content of article dataset',
        args: {
          id: {
            name: 'id',
            type: new GraphQLNonNull(GraphQLID)
          },

          // Edited this part to add the language tag
          languageTag: {
            name: 'languageTag',
            type: new GraphQLNonNull(GraphQLString)
          }
        },

        async resolve ({ db }, args) {
          return db.collection('articles').find({ main: args.id }).toArray()
        }
      }
    }   
  })
})

However, this still does not fix your issue of retrieving the content. I reckon that you need to add another type to your schema called ContentType.

const ContentType = new GraphQLObjectType({
  name: 'ContentType',
  fields: {
    content: {
      type: GraphQLString,
      resolve: (root, args, context) => root.content[args.languageTag].content
    },
    timestamp: {
      type: GraphQLString,
      resolve: (root, args, context) => root.content[args.languageTag].timestamp
    }
  },
})

One final issue I would like to bring up is that you are returning a single article as an Array. I would suggest to change this to return a single object. Last but not least, your schema would look something like this:

export default new GraphQLSchema({
  query: new GraphQLObjectType({
    name: 'RootQueryType',
    fields: {
      article: {
        type: new GraphQLList(ArticleType),
        description: 'Content of article dataset',
        args: {
          id: {
            name: 'id',
            type: new GraphQLNonNull(GraphQLID)
          },

          // Edited this part to add the language tag
          languageTag: {
            name: 'languageTag',
            type: new GraphQLNonNull(GraphQLString)
        },

        // Add the extra fields to the article
        fields: {
          content: ContentType
        }

        async resolve ({ db }, args) {
          return db.collection('articles').findOne({ main: args.id })
        }
      }
    }   
  })
})

This code could be a little bit off, since I do not have your database to test it. I think that it is a good push in the right direction.

DevNebulae
  • 4,566
  • 3
  • 16
  • 27
  • The language field is an array, as there are multiple content objects. In my example there is only one object. But I need to go for the array. – user3142695 Oct 28 '17 at 18:37
  • @user3142695 Could you give a schema of your problem? I think we're missing a big piece of the puzzle. – DevNebulae Oct 28 '17 at 18:40
  • I think I understand what you are trying to do, but still I get the result `"content": "[object Object]"` – user3142695 Oct 28 '17 at 18:46
  • Is the resolve field triggering in the `ContentType`? I.e. have you put a `console.log` there and see if it gets reached? – DevNebulae Oct 28 '17 at 18:55
  • I tried exactly this right in the moment. No, the resolve in `ContentType` doesn't fire a `console.log` – user3142695 Oct 28 '17 at 18:56
  • @user3142695 I just updated the `ContentType`, what about now? – DevNebulae Oct 28 '17 at 18:59
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/157720/discussion-between-user3142695-and-devnebulae). – user3142695 Oct 28 '17 at 19:01
  • 1
    I have to move the extra content field to `ArticleType` and add the language argument and the resolve there. With that it is working. Thanks for your help and your hint. – user3142695 Oct 29 '17 at 21:22
1

You can use below code.

String query = {
  article(id: "Dn59y87PGhkJXpaiZ") {
    _id,
    content(language:"en") {
      content,
      timestamp
    }
  }
}

const ContentType = new GraphQLObjectType({
  name: 'content',
  fields: {
    content: { type: GraphQLString },
    timestamp: { type: GraphQLInt }
  }
})

const ArticleType = new GraphQLObjectType({
  name: 'article',
  fields: {
    _id: { type: GraphQLID },
    content: { 
      type: new GraphQLList(ContentType),
      args: {
          language: {
            name: 'language',
            type: new GraphQLNonNull(GraphQLString)
          }
        },
        async resolve (args) {
          return filter content here by lang 
        }
      }
    }
  }
})

export default new GraphQLSchema({
  query: new GraphQLObjectType({
    name: 'RootQueryType',
    fields: {
      article: {
        type: new GraphQLList(ArticleType),
        description: 'Content of article dataset',
        args: {
          id: {
            name: 'id',
            type: new GraphQLNonNull(GraphQLID)
          }
        },
        async resolve ({ db }, args) {
          return db.collection('articles').find({ main: args.id}).toArray()
        }
      }
    }   
  })
})

Java Example:

import com.mongodb.MongoClient;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.Filters;
import graphql.ExecutionResult;
import graphql.GraphQL;
import graphql.schema.*;
import org.bson.Document;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import static graphql.Scalars.*;
import static graphql.schema.GraphQLArgument.newArgument;
import static graphql.schema.GraphQLFieldDefinition.newFieldDefinition;
import static graphql.schema.GraphQLObjectType.newObject;
import static graphql.schema.GraphQLSchema.newSchema;


public class GraphQLTest {

    private static final ArticleRepository articleRepository;

    public static class ArticleRepository {

        private final MongoCollection<Document> articles;

        ArticleRepository(MongoCollection<Document> articles) {
            this.articles = articles;
        }

        public List<Map<String, Object>> getAllArticles(String id) {
            List<Map<String, Object>>  allArticles = articles.find(Filters.eq("main", id)).map(doc -> (Map<String, Object>)doc).into(new ArrayList<>());
            return allArticles;
        }

    }

    public static void main(String... args) {

        String query = "{\n" +
                "  article(id: \"Dn59y87PGhkJXpaiZ\") {\n" +
                "    _id,\n" +
                "    content(language:\"en\") {\n" +
                "      content,\n" +
                "      timestamp\n" +
                "    }\n" +
                "  }\n" +
                "}";

        ExecutionResult result = GraphQL.newGraphQL(buildSchema()).build().execute(query);

        System.out.print(result.getData().toString());
    }


    static {
        MongoDatabase mongo = new MongoClient().getDatabase("test");
        articleRepository = new ArticleRepository(mongo.getCollection("articles"));
    }

    private static GraphQLSchema buildSchema() {

        GraphQLObjectType ContentType = newObject().name("content")
                .field(newFieldDefinition().name("content").type(GraphQLString).build())
                .field(newFieldDefinition().name("timestamp").type(GraphQLInt).build()).build();

        GraphQLObjectType ArticleType = newObject().name("article")
                .field(newFieldDefinition().name("_id").type(GraphQLID).build())
                .field(newFieldDefinition().name("content").type(new GraphQLList(ContentType))
                        .argument(newArgument().name("language").type(GraphQLString).build())
                        .dataFetcher(dataFetchingEnvironment -> {
                            Document source = dataFetchingEnvironment.getSource();
                            Document contentMap = (Document) source.get("content");
                            ArrayList<Document> contents = (ArrayList<Document>) contentMap.get(dataFetchingEnvironment.getArgument("lang"));
                            return contents;
                        }).build()).build();

        GraphQLFieldDefinition.Builder articleDefinition = newFieldDefinition()
                .name("article")
                .type(new GraphQLList(ArticleType))
                .argument(newArgument().name("id").type(new GraphQLNonNull(GraphQLID)).build())
                .dataFetcher(dataFetchingEnvironment -> articleRepository.getAllArticles(dataFetchingEnvironment.getArgument("id")));

        return newSchema().query(
                newObject()
                        .name("RootQueryType")
                        .field(articleDefinition)
                        .build()
        ).build();
    }
}
s7vr
  • 73,656
  • 11
  • 106
  • 127
  • Do you have also a hint for this? https://stackoverflow.com/questions/46929327/how-to-nest-two-graphql-queries-in-a-schema – user3142695 Oct 29 '17 at 21:36
  • That question seems very similar to this one. Did you change the structure to a new one ? Is there a new collection content now ? I believe the answer provided here will work to some extent. What are the differences ? – s7vr Oct 29 '17 at 21:48
  • I changed it only slightly for the example code. I really get stucked on that nesting. First I thought it should be quite simple as you are right that it seems very similar problem... – user3142695 Oct 29 '17 at 21:51
0

I think the problem comes from this line: content: { type: GraphQLString }. Try extracting content to another GraphQLObjectType and then pass it to ArticleType as content field

Edit

Try this:

const ContentType = new GraphQLObjectType({
  name: 'content',
  fields: {
    en: { type: GraphQLList },
    it: { type: GraphQLList }
    // ... other languages 
  }
})
Alexander Vitanov
  • 4,074
  • 2
  • 19
  • 22
  • For me there are two problems with that? The field `en` is variable. There could also be `de` or `it`. So I need to use a variable to get the correct langugage object. I'm stucked on that. And the second thing is, that this is an array, so `GraphQLString` won't work here, right? Could you please post some code for better understanding? – user3142695 Oct 28 '17 at 18:34