Return Map from GraphQL (#11966)

* Return Map from GraphQL

Three techniques have been used

1. Return as Json String
2. Return Json using GraphQL scalar type
3. Return as list of key-value pair

* Adding custom scalar in plugin configuration

* Build failure fix
1. Integrating the .graphqls files
2. Updating the respective query resolvers

* Build failure Fix

1. Removed the extra .graphql file

2. Added ExtendedGraphQLScalarType class because the parent class didn't 
have default constructor and the client code generation plugin was 
requiring it.

* Code refactoring

* Code refactoring
This commit is contained in:
Muhammad Abdullah Azam Khan 2022-04-21 16:01:36 +04:00 committed by GitHub
parent 936055fd69
commit ce2839b058
13 changed files with 398 additions and 1 deletions

View File

@ -36,6 +36,21 @@
<artifactId>graphql-java-annotations</artifactId> <artifactId>graphql-java-annotations</artifactId>
<version>${graphql-java-annotations.version}</version> <version>${graphql-java-annotations.version}</version>
</dependency> </dependency>
<dependency>
<groupId>io.ratpack</groupId>
<artifactId>ratpack-core</artifactId>
<version>${ratpack-core.version}</version>
</dependency>
<dependency>
<groupId>com.github.americanexpress.nodes</groupId>
<artifactId>nodes</artifactId>
<version>0.5.0</version>
</dependency>
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java</artifactId>
<version>11.0</version>
</dependency>
<dependency> <dependency>
<groupId>com.graphql-java</groupId> <groupId>com.graphql-java</groupId>
<artifactId>graphql-java-tools</artifactId> <artifactId>graphql-java-tools</artifactId>
@ -95,6 +110,14 @@
<version>${mockserver-client-java.version}</version> <version>${mockserver-client-java.version}</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java-extended-scalars</artifactId>
<version>${graphql-java-extended-scalars.version}</version>
</dependency>
</dependencies> </dependencies>
<build> <build>
@ -133,6 +156,13 @@
<copyRuntimeSources>false</copyRuntimeSources> <copyRuntimeSources>false</copyRuntimeSources>
<generateDeprecatedRequestResponse>false</generateDeprecatedRequestResponse> <generateDeprecatedRequestResponse>false</generateDeprecatedRequestResponse>
<separateUtilityClasses>true</separateUtilityClasses> <separateUtilityClasses>true</separateUtilityClasses>
<customScalars>
<customScalar>
<graphQLTypeName>JSON</graphQLTypeName>
<javaType>java.util.Map</javaType>
<graphQLScalarTypeClass>com.baeldung.graphqlreturnmap.ExtendedGraphQLScalarType</graphQLScalarTypeClass>
</customScalar>
</customScalars>
</configuration> </configuration>
</plugin> </plugin>
</plugins> </plugins>
@ -154,6 +184,8 @@
<maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target> <maven.compiler.target>1.8</maven.compiler.target>
<graphql.java.generator.version>1.18</graphql.java.generator.version>
<graphql-java-extended-scalars.version>2022-04-06T00-10-27-a70541e</graphql-java-extended-scalars.version>
</properties> </properties>
</project> </project>

View File

@ -2,6 +2,7 @@ package com.baeldung.graphql.server;
import com.baeldung.graphql.data.Book; import com.baeldung.graphql.data.Book;
import com.baeldung.graphql.data.BookRepository; import com.baeldung.graphql.data.BookRepository;
import com.baeldung.graphqlreturnmap.entity.Product;
import com.coxautodev.graphql.tools.GraphQLQueryResolver; import com.coxautodev.graphql.tools.GraphQLQueryResolver;
import java.util.List; import java.util.List;
@ -18,4 +19,12 @@ public class GraphQLQuery implements GraphQLQueryResolver {
return repository.getAllBooks(); return repository.getAllBooks();
} }
public List<Product> getProducts(int pageSize, int pageNumber) {
return null;
}
public Product getProduct(int id) {
return null;
}
} }

View File

@ -0,0 +1,52 @@
package com.baeldung.graphqlreturnmap;
import com.baeldung.graphql.utils.SchemaUtils;
import com.baeldung.graphqlreturnmap.resolver.ProductResolver;
import com.baeldung.graphqlreturnmap.resolver.Query;
import com.coxautodev.graphql.tools.SchemaParser;
import graphql.ExecutionResult;
import graphql.GraphQL;
import graphql.scalars.ExtendedScalars;
import graphql.schema.GraphQLSchema;
import ratpack.handling.Context;
import ratpack.handling.Handler;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.logging.Logger;
import static ratpack.jackson.Jackson.json;
public class AppHandler implements Handler {
private static final Logger LOGGER = Logger.getLogger(AppHandler.class.getSimpleName());
private GraphQL graphql;
public AppHandler() throws Exception {
GraphQLSchema schema = SchemaParser.newParser()
.resolvers(new Query(), new ProductResolver())
.scalars(ExtendedScalars.Json)
.file("schema.graphqls")
.build()
.makeExecutableSchema();
graphql = GraphQL.newGraphQL(schema).build();
}
@Override
public void handle(Context context) throws Exception {
context.parse(Map.class)
.then(payload -> {
ExecutionResult executionResult = graphql.execute(payload.get(SchemaUtils.QUERY)
.toString(), null, this, Collections.emptyMap());
Map<String, Object> result = new LinkedHashMap<>();
if (executionResult.getErrors()
.isEmpty()) {
result.put(SchemaUtils.DATA, executionResult.getData());
} else {
result.put(SchemaUtils.ERRORS, executionResult.getErrors());
LOGGER.warning("Errors: " + executionResult.getErrors());
}
context.render(json(result));
});
}
}

View File

@ -0,0 +1,24 @@
package com.baeldung.graphqlreturnmap;
import graphql.language.ScalarTypeDefinition;
import graphql.schema.Coercing;
import graphql.schema.GraphQLDirective;
import graphql.schema.GraphQLScalarType;
import java.util.List;
public class ExtendedGraphQLScalarType extends GraphQLScalarType {
public ExtendedGraphQLScalarType(){
super("","",null);
}
public ExtendedGraphQLScalarType(String name, String description, Coercing coercing) {
super(name, description, coercing);
}
public ExtendedGraphQLScalarType(String name, String description, Coercing coercing, List<GraphQLDirective> directives, ScalarTypeDefinition definition) {
super(name, description, coercing, directives, definition);
}
}

View File

@ -0,0 +1,12 @@
package com.baeldung.graphqlreturnmap;
import ratpack.server.RatpackServer;
public class GraphqlReturnMap {
public static void main(String[] args) throws Exception {
final RatpackServer server = RatpackServer.of(s -> s.handlers(chain -> chain.post("product", new AppHandler())));
server.start();
}
}

View File

@ -0,0 +1,46 @@
package com.baeldung.graphqlreturnmap.entity;
public class Attribute {
private String name;
private String description;
private String unit;
public Attribute(String name, String description, String unit){
this.name = name;
this.description = description;
this.unit = unit;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getUnit() {
return unit;
}
public void setUnit(String unit) {
this.unit = unit;
}
@Override
public String toString() {
return "Attribute{" +
"name='" + name + '\'' +
", description='" + description + '\'' +
", unit='" + unit + '\'' +
'}';
}
}

View File

@ -0,0 +1,48 @@
package com.baeldung.graphqlreturnmap.entity;
import java.util.Map;
public class Product {
private Integer id;
private String name;
private String description;
private Map<String, Attribute> attributes;
public Product(){
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Map<String, Attribute> getAttributes() {
return attributes;
}
public void setAttributes(Map<String, Attribute> attributes) {
this.attributes = attributes;
}
}

View File

@ -0,0 +1,30 @@
package com.baeldung.graphqlreturnmap.model;
import com.baeldung.graphqlreturnmap.entity.Attribute;
public class AttributeKeyValueModel {
private String key;
private Attribute value;
public AttributeKeyValueModel(String key, Attribute value) {
this.key = key;
this.value = value;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public Attribute getValue() {
return value;
}
public void setValue(Attribute value) {
this.value = value;
}
}

View File

@ -0,0 +1,12 @@
package com.baeldung.graphqlreturnmap.repository;
import com.baeldung.graphqlreturnmap.entity.Product;
import java.util.List;
public interface ProductRepository {
List<Product> getProducts(Integer pageSize, Integer pageNumber);
Product getProduct(Integer id);
}

View File

@ -0,0 +1,43 @@
package com.baeldung.graphqlreturnmap.repository.impl;
import com.baeldung.graphqlreturnmap.entity.Attribute;
import com.baeldung.graphqlreturnmap.entity.Product;
import com.baeldung.graphqlreturnmap.repository.ProductRepository;
import org.springframework.stereotype.Repository;
import java.util.*;
import java.util.stream.Collectors;
@Repository
public class ProductRepositoryImpl implements ProductRepository {
private static List<Product> productList = new ArrayList<>();
public ProductRepositoryImpl() {
for (int i = 1; i <= 10; i++){
Product product = new Product();
product.setId(i);
product.setName(String.format("Product %d", i));
product.setDescription(String.format("Product %d description", i));
product.setAttributes(createAttributes(i));
productList.add(product);
}
}
private Map<String, Attribute> createAttributes(int i) {
Map<String, Attribute> attributeMap = new HashMap<>();
attributeMap.put(String.format("attribute_%d",i), new Attribute(String.format("Attribute%d name",i),"This is custom attribute description","This is custom attribute unit"));
attributeMap.put("size", new Attribute((i & 1) == 0 ? "Small" : "Large","This is custom attribute description","This is custom attribute unit"));
return attributeMap;
}
@Override
public List<Product> getProducts(Integer pageSize, Integer pageNumber) {
return productList.stream().skip(pageSize*pageNumber).limit(pageSize).collect(Collectors.toList());
}
@Override
public Product getProduct(Integer id) {
return productList.stream().filter(product -> product.getId().equals(id)).findFirst().orElse(null);
}
}

View File

@ -0,0 +1,30 @@
package com.baeldung.graphqlreturnmap.resolver;
import com.baeldung.graphqlreturnmap.entity.Product;
import com.baeldung.graphqlreturnmap.model.AttributeKeyValueModel;
import com.coxautodev.graphql.tools.GraphQLResolver;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.LinkedList;
import java.util.List;
public class ProductResolver implements GraphQLResolver<Product> {
public ProductResolver(){
}
public List<AttributeKeyValueModel> getAttribute_list(Product product){
List<AttributeKeyValueModel> attributeModelList = new LinkedList<>();
product.getAttributes().forEach((key, val) -> attributeModelList.add(new AttributeKeyValueModel(key, val)));
return attributeModelList;
}
public String getAttribute_string(Product product){
try {
return new ObjectMapper().writeValueAsString(product.getAttributes());
} catch (JsonProcessingException e) {
e.printStackTrace();
return "";
}
}
}

View File

@ -0,0 +1,33 @@
package com.baeldung.graphqlreturnmap.resolver;
import com.baeldung.graphql.data.Book;
import com.baeldung.graphqlreturnmap.entity.Product;
import com.baeldung.graphqlreturnmap.repository.ProductRepository;
import com.baeldung.graphqlreturnmap.repository.impl.ProductRepositoryImpl;
import com.coxautodev.graphql.tools.GraphQLQueryResolver;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
public class Query implements GraphQLQueryResolver {
@Autowired
private ProductRepository productRepository;
public Query(){
productRepository = new ProductRepositoryImpl();
}
public List<Product> getProducts(int pageSize, int pageNumber) {
return productRepository.getProducts(pageSize, pageNumber);
}
public Product getProduct(int id) {
return productRepository.getProduct(id);
}
public List<Book> allBooks() {
return null;
}
}

View File

@ -10,8 +10,34 @@ type Author {
type Query { type Query {
allBooks: [Book] allBooks: [Book]
products(size: Int, page: Int): [Product]!
product(id: Int): Product!
} }
type Product {
id: ID
name: String!
description: String
attribute_string:String
attribute_list:[AttributeKeyValuePair]
attributes: JSON
}
type AttributeKeyValuePair {
key:String
value:Attribute
}
type Attribute {
name:String
description:String
unit:String
}
scalar JSON
schema { schema {
query: Query query: Query
} }