1. 概述
多年来,GraphQL 已经被广泛接受为Web服务通信模式之一。尽管它功能丰富且灵活,但在某些场景下可能会遇到挑战,其中之一是查询时返回一个Map
。因为Map
在GraphQL中不是一个类型,这构成了一种挑战。
本教程将学习如何从GraphQL查询中返回Map
的技术。
2. 示例
以一个包含无限数量自定义属性的产品数据库为例。
作为数据库实体,每个产品可能有固定的字段,如name
、price
和category
等。但它们也可能有根据类别变化的属性。这些属性需要以保留标识键的方式返回给客户端。
为此,我们可以使用Map
作为这些属性的类型。
3. 返回Map
为了返回Map
,我们有三种选择:
- 作为JSON字符串返回
- 使用GraphQL的自定义标量类型
- 作为键值对列表返回
接下来,我们将探讨所有三个选项。
3.1. JSON字符串
这是最简单的选择。在产品解析器中,我们将Map
转换为JSON字符串格式:
query {
product(id:1){
id
name
description
attributes
}
}
GraphQL的schema如下:
type Product {
id: ID
name: String!
description: String
attributes:String
}
实施后查询的结果如下:
{
"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\"
}
}"
}
}
}
这个方法存在两个问题:首先,客户端需要处理JSON字符串才能得到可用格式;其次,无法对属性进行子查询。
为了解决第一个问题,我们可以借助GraphQL自定义标量类型。
3.2. GraphQL自定义标量类型
在Java中,我们将使用Extended Scalars库来实现。首先,在pom.xml
中添加依赖:
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java-extended-scalars</artifactId>
<version>2022-04-06T00-10-27-a70541e</version>
</dependency>
然后,在GraphQL配置组件中注册所需的标量类型(这里选择JSON
):
@Bean
public GraphQLScalarType json() {
return ExtendedScalars.Json;
}
最后更新GraphQL schema:
type Product {
id: ID
name: String!
description: String
attributes: JSON
}
scalar JSON
实施后的结果如下:
{
"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"
}
}
}
}
}
这种方法无需在客户端处理属性Map
,但标量类型有自己的限制。
在GraphQL中,标量类型是查询的叶子节点,这意味着它们不能进一步查询。
3.3. 键值对列表
如果需要进一步查询Map
,这是最可行的选择。我们将Map
对象转换为键值对对象的列表。
这是代表键值对的类:
public class AttributeKeyValueModel {
private String key;
private Attribute value;
public AttributeKeyValueModel(String key, Attribute value) {
this.key = key;
this.value = value;
}
}
在产品解析器中添加以下实现:
List<AttributeKeyValueModel> attributeModelList = new LinkedList<>();
product.getAttributes().forEach((key, val) -> attributeModelList.add(new AttributeKeyValueModel(key, val)));
最后更新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
}
由于更新了schema,我们也需要更新查询:
query {
product(id:1){
id
name
description
attributes {
key
value {
name
description
unit
}
}
}
}
现在来看结果:
{
"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"
}
}
]
}
}
}
这个选项也有两个问题:查询变得更复杂,且对象结构需要硬编码。未知的Map
对象在这种情况下无法工作。
4. 总结
在这篇文章中,我们探讨了从GraphQL查询中返回Map
对象的三种不同方法及其局限性。根据需求,应选择最适合的方法。
如往常一样,本文的示例代码可在GitHub上找到。