Return Map from GraphQL – 从GraphQL返回地图

最后修改: 2022年 4月 22日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

Over the years, GraphQL has been widely accepted as one of the patterns of communication for web services. Though it’s rich and flexible in use, it may pose challenges in some scenarios. One of them is to return a Map from a query, which is a challenge since a Map is not a type in GraphQL.

多年来,GraphQL已被广泛接受为Web服务的通信模式之一。虽然它的内容丰富,使用灵活,但在某些情况下可能会带来挑战。其中之一是从查询中返回一个Map,这是一个挑战,因为Map在GraphQL中不是一个类型。

In this tutorial, we’ll learn techniques to return a Map from a GraphQL query.

在本教程中,我们将学习从GraphQL查询中返回一个Map的技术。

2. Example

2.例子

Let’s take the example of a product database having an indefinite number of custom attributes.

让我们举个例子,一个产品数据库有无限多的自定义属性。

A Product, as a database entity, may have some fixed fields like name, price, category, etc. But, it may also have attributes that vary from category to category. These attributes should be returned to the client in a way that their identifying keys remain preserved.

一个产品,作为一个数据库实体,可能有一些固定的字段,如名称价格类别,等等。但是,它也可能有不同类别的属性。这些属性应该以一种保持其识别键的方式返回给客户。

For that purpose, we can make use of a Map as the type of these attributes.

为此,我们可以使用Map作为这些属性的类型。

3. Return Map

3.返回地图

In order to return a Map, we’ve got three options:

为了返回一个地图,我们有三个选择。

  • Return as JSON String
  • Use GraphQL custom scalar type
  • Return as List of key-value pairs

For the first two options, we’ll be making use of the following GraphQL query:

对于前两个选项,我们将利用以下GraphQL查询。

query {
    product(id:1){ 
        id 
        name 
        description 
        attributes 
    }
}

The parameter attributes will be represented in the Map format.

参数attributes将以Map格式表示。

Next, let’s look at all three options.

接下来,让我们看看所有这三个选项。

3.1. JSON String

3.1 JSON字符串

This is the simplest option. We’ll serialize the Map into JSON String format in the Product resolver:

这是最简单的选择。我们将在Product解析器中把Map序列化为JSONString格式。

String attributes = objectMapper.writeValueAsString(product.getAttributes());

The GraphQL schema itself is as follows:

GraphQL模式本身如下。

type Product {
    id: ID
    name: String!
    description: String
    attributes:String
}

Here’s the result of the query after this implementation:

下面是这样实现后的查询结果。

{
  "data": {
    "product": {
      "id": "1",
      "name": "Product 1",
      "description": "Product 1 description",
      "attributes": "{\"size\": {
                                     \"name\": \"Large\",
                                     \"description\": \"This is custom attribute description\",
                                     \"unit\": \"This is custom attribute unit\"
                                    },
                   \"attribute_1\": {
                                     \"name\": \"Attribute1 name\",
                                     \"description\": \"This is custom attribute description\",
                                     \"unit\": \"This is custom attribute unit\"
                                    }
                        }"
    }
  }
}

This option has two issues. The first issue is that the JSON string needs to be processed on the client-side into a workable format. The second issue is that we can’t have a sub-query on attributes.

这个选项有两个问题。第一个问题是,JSON字符串需要在客户端被处理成可行的格式。第二个问题是我们不能在属性上有一个子查询。

To overcome the first issue, the second option of GraphQL custom scalar type can help.

为了克服第一个问题,GraphQL自定义标量类型的第二个选项可以提供帮助。

3.2. GraphQL Custom Scalar Type

3.2 GraphQL自定义标量类型

For the implementation, we’ll make use of the Extended Scalars library for GraphQL in Java.

在实现方面,我们将利用Java中用于GraphQL的Extended Scalars库。

Firstly, we’ll include the graphql-java-extended-scalars dependency in pom.xml:

首先,我们将在pom.xml中包含graphql-java-extended-scalars依赖项。

<dependency>
    <groupId>com.graphql-java</groupId>
    <artifactId>graphql-java-extended-scalars</artifactId>
    <version>2022-04-06T00-10-27-a70541e</version>
</dependency>

Then, we’ll register the scalar type of our choice in the GraphQL configuration component. In this case, the scalar type is JSON:

然后,我们将在GraphQL配置组件中注册我们选择的标量类型。在这种情况下,标量类型是JSON

@Bean
public GraphQLScalarType json() {
    return ExtendedScalars.Json;
}

Lastly, we’ll update our GraphQL schema accordingly:

最后,我们将相应地更新我们的GraphQL模式。

type Product {
    id: ID
    name: String!
    description: String
    attributes: JSON
}
scalar JSON

Here’s the result after this implementation:

下面是这样实施后的结果。

{
  "data": {
    "product": {
      "id": "1",
      "name": "Product 1",
      "description": "Product 1 description",
      "attributes": {
        "size": {
          "name": "Large",
          "description": "This is custom attribute description",
          "unit": "This is a custom attribute unit"
        },
        "attribute_1": {
          "name": "Attribute1 name",
          "description": "This is custom attribute description",
          "unit": "This is a custom attribute unit"
        }
      }
    }
  }
}

With this approach, we won’t need to process the attributes map on the client-side. However, the scalar types come with their own limitations.

使用这种方法,我们就不需要在客户端处理属性图了。然而,标量类型也有其自身的局限性。

In GraphQL, scalar types are the leaves of the query, which suggests that they can’t be queried further.

在GraphQL中,标量类型是查询的叶子,这表明它们不能被进一步查询了。

3.3. List of Key-Value Pairs

3.3.键值对列表

If the requirement is to query further into the Map, then this is the most feasible option. We’ll transform the Map object into a list of key-value pair objects.

如果需求是进一步查询Map,那么这是最可行的方案。我们将把Map对象转化为键值对对象的列表。

Here’s our class representing a key-value pair:

这里是我们的类,代表一个键值对。

public class AttributeKeyValueModel {
    private String key;
    private Attribute value;
    
    public AttributeKeyValueModel(String key, Attribute value) {
        this.key = key;
        this.value = value;
    }
}

In the Product resolver, we’ll add the following implementation:

Product解析器中,我们将添加以下实现。

List<AttributeKeyValueModel> attributeModelList = new LinkedList<>();
product.getAttributes().forEach((key, val) -> attributeModelList.add(new AttributeKeyValueModel(key, val)));

Finally, we’ll update the schema:

最后,我们将更新模式。

type Product {
    id: ID
    name: String!
    description: String
    attributes:[AttributeKeyValuePair]
}
type AttributeKeyValuePair {
    key:String
    value:Attribute
}
type Attribute {
    name:String
    description:String
    unit:String
}

Since we’ve updated the schema, we’ll update the query as well:

既然我们已经更新了模式,我们也要更新查询。

query {
    product(id:1){ 
         id 
         name 
         description 
         attributes {
             key 
             value {
                 name 
                 description 
                 unit
             }
        } 
    }
}

Now, let’s look at the result:

现在,让我们看看结果。

{
  "data": {
    "product": {
      "id": "1",
      "name": "Product 1",
      "description": "Product 1 description",
      "attributes": [
        {
          "key": "size",
          "value": {
            "name": "Large",
            "description": "This is custom attribute description",
            "unit": "This is custom attribute unit"
          }
        },
        {
          "key": "attribute_1",
          "value": {
            "name": "Attribute1 name",
            "description": "This is custom attribute description",
            "unit": "This is custom attribute unit"
          }
        }
      ]
    }
  }
}

This option also has two issues. The GraphQL query has become a bit complex. And the object structure needs to be hardcoded. Unknown Map objects won’t work in this case.

这个选项也有两个问题。GraphQL查询变得有点复杂。而且对象结构需要硬编码。未知的Map对象在这种情况下不会工作。

4. Conclusion

4.总结

In this article, we’ve looked into three different techniques to return a Map object from a GraphQL query. We discussed the limitations of each of them. Since none of the techniques is perfect, they must be used based on the requirements.

在这篇文章中,我们研究了从GraphQL查询中返回Map对象的三种不同技术。我们讨论了它们各自的局限性。由于没有一种技术是完美的,所以必须根据需求来使用它们。

As always, the example code for this article is available over on GitHub.

像往常一样,本文的示例代码可在GitHub上获得