Query Functions
Query Functions字段值的迭代与操作

字段值的迭代与操作

Included in the “Power Extensions” bundle

向 GraphQL 模式添加元指令,用于迭代和操作数组及对象字段的值元素:

  1. @underArrayItem
  2. @underJSONObjectProperty
  3. @underEachArrayItem
  4. @underEachJSONObjectProperty
  5. @objectClone

@underArrayItem

@underArrayItem 使嵌套指令作用于数组中的特定元素。

在下面的 query 中,只有分类名称数组中的第一个元素被转换为大写:

query {
  posts {
    categoryNames
      @underArrayItem(index: 0)
        @strUpperCase
  }
}

...结果:

{
  "data": {
    "posts": {
      "categoryNames": [
        "NEWS",
        "sports"
      ]
    }
  }
}

@underJSONObjectProperty

@underJSONObjectProperty 使嵌套指令能够接收被查询 JSON 对象中的某个条目。

该指令在查询外部 API 后提取并操作所需数据时特别有用,因为外部 API 几乎肯定会有一个泛型 JSONObject 类型(例如使用 HTTP Client 扩展的函数字段 _sendJSONObjectItemHTTPRequest 时)。

在下面的 query 中,我们获取来自 WP REST API 的 JSON 对象,并使用 @underJSONObjectProperty 操作响应的 type 属性,将其转换为大写:

query {
  postData: _sendJSONObjectItemHTTPRequest(input: {
    url: "https://newapi.getpop.org/wp-json/wp/v2/posts/1/?_fields=id,type,title,date"
  })
    @underJSONObjectProperty(by: { key: "type" })
      @strUpperCase
}

这将产生:

{
  "data": {
    "postData": {
      "id": 1,
      "date": "2019-08-02T07:53:57",
      "type": "POST",
      "title": {
        "rendered": "Hello world!"
      }
    }
  }
}

除了接收 "key" 来指向 JSON 对象第一层的属性外,该指令还可以接收 "path" 来导航对象的内部结构,使用 . 作为层级分隔符。

在下面的 query 中,WP REST API 的文章端点提供了属性 "title.rendered"。我们可以导航到该实际子元素,并将其转换为标题大小写:

query {
  postData: _sendJSONObjectItemHTTPRequest(input: {
    url: "https://newapi.getpop.org/wp-json/wp/v2/posts/1/?_fields=id,type,title,date"
  })
    @underJSONObjectProperty(by: { path: "title.rendered" })
      @strTitleCase
}

这将产生:

{
  "data": {
    "postData": {
      "id": 1,
      "date": "2019-08-02T07:53:57",
      "type": "post",
      "title": {
        "rendered": "HELLO WORLD!"
      }
    }
  }
}

@underEachArrayItem

@underEachArrayItem 遍历被查询实体某个字段的数组元素,并对每个元素执行嵌套指令。

例如,字段 Post.categoryNames 的类型为 [String]。使用 @underEachArrayItem,可以遍历分类名称并对其应用 @strTranslate 指令。

在此 query 中,文章的分类从英语翻译为法语:

query {
  posts {
    id
    title
    categoryNames
      @underEachArrayItem
        @strTranslate(
          from: "en",
          to: "fr"
        )
  }
}

...结果:

{
  "data": {
    "posts": [
      {
        "id": 662,
        "title": "Explaining the privacy policy",
        "categoryNames": [
          "Non classé"
        ]
      },
      {
        "id": 28,
        "title": "HTTP caching improves performance",
        "categoryNames": [
          "Avancé"
        ]
      },
      {
        "id": 25,
        "title": "Public or Private API mode, for extra security",
        "categoryNames": [
          "Ressource",
          "Blog",
          "Avancé"
        ]
      }
    ]
  }
}

@underEachArrayItem 可以通过指令参数 passIndexOnwardsAspassValueOnwardsAs,将迭代元素的索引和值作为动态变量传递给嵌套指令。

此 query 演示了动态变量 $index$value 的用法:

{
  _echo(value: ["first", "second", "third"])
    @underEachArrayItem(
      passIndexOnwardsAs: "index"
      passValueOnwardsAs: "value"
    )
      @applyField(
        name: "_echo"
        arguments: {
          value: {
            index: $index,
            value: $value
          }
        },
        setResultInResponse: true
      )
}

结果为:

{
  "data": {
    "_echo": [
      {
        "index": 0,
        "value": "first"
      },
      {
        "index": 1,
        "value": "second"
      },
      {
        "index": 2,
        "value": "third"
      }
    ]
  }
}

@underEachArrayItem 还可以通过参数 filter->by 限制要迭代的数组位置,该参数可接受 includeexclude 条目。

此 query:

{
  including: _echo([
    "first",
    "second",
    "third"
  ])
    @underEachArrayItem(
      filter: {
        by: {
          include: [0, 2]
        }
      }
    )
      @strUpperCase
 
  excluding: _echo([
    "first",
    "second",
    "third"
  ])
    @underEachArrayItem(
      filter: {
        by: {
          exclude: [0, 2]
        }
      }
    )
      @strUpperCase
}

...结果:

{
  "data": {
    "including": [
      "FIRST",
      "second",
      "THIRD"
    ],
    "excluding": [
      "first",
      "SECOND",
      "third"
    ]
  }
}

@underEachJSONObjectProperty

@underEachJSONObjectProperty@underEachArrayItem 类似,但作用于 JSONObject 元素。

在此 query 中,我们遍历 JSON 对象中的所有条目,并将任何 null 条目替换为空字符串:

{
  _echo(
    value: {
      first: "hello",
      second: "world",
      third: null
    }
  )
    @underEachJSONObjectProperty
      @default(value: "")
}

...结果:

{
  "data": {
    "_echo": {
      "first": "hello",
      "second": "world",
      "third": ""
    }
  }
}

@underEachJSONObjectProperty 可以通过指令参数 passKeyOnwardsAspassValueOnwardsAs,将迭代的键和值作为动态变量传递给嵌套指令。

此 query 演示了动态变量 $key$value 的用法:

{
  _echo(value: {
    uno: "first",
    dos: "second",
    tres: "third"
  })
    @underEachJSONObjectProperty(
      passKeyOnwardsAs: "key"
      passValueOnwardsAs: "value"
    )
      @applyField(
        name: "_echo"
        arguments: {
          value: {
            key: $key,
            value: $value
          }
        },
        setResultInResponse: true
      )
}

结果为:

{
  "data": {
    "_echo": {
      "uno": {
        "key": "uno",
        "value": "first"
      },
      "dos": {
        "key": "dos",
        "value": "second"
      },
      "tres": {
        "key": "tres",
        "value": "third"
      }
    }
  }
}

@underEachJSONObjectProperty 还可以通过参数 filter->by 限制要迭代的 JSON 对象键,该参数可接受 includeKeysexcludeKeys 条目。

此 query:

{
  includingKeys: _echo(value: {
    uno: "first",
    dos: "second",
    tres: "third"
  })
    @underEachJSONObjectProperty(
      filter: {
        by: {
          includeKeys: ["uno", "tres"]
        }
      }
    )
      @strUpperCase
 
  excludingKeys: _echo(value: {
    uno: "first",
    dos: "second",
    tres: "third"
  })
    @underEachJSONObjectProperty(
      filter: {
        by: {
          excludeKeys: ["uno", "tres"]
        }
      }
    )
      @strUpperCase
}

...结果:

{
  "data": {
    "includingKeys": {
      "uno": "FIRST",
      "dos": "second",
      "tres": "THIRD"
    },
    "excludingKeys": {
      "uno": "first",
      "dos": "SECOND",
      "tres": "third"
    }
  }
}

@objectClone

JSON 对象在字段解析器中可能以引用方式访问(而非复制/复制对象)。当这种情况发生时,JSON 对象被修改后,修改将对所有获取该 JSON 对象的字段可见。

字段 Block.attributes 就属于这种情况:

{
  posts {
    blocks(filterBy: { include: "core/heading" } ) {
      attributes
    }
  }
}

...结果:

{
  "data": {
    "posts": [
      {
        "blocks": [
          {
            "attributes": {
              "content": "Image Block (Full width)",
              "level": 2
            }
          },
          {
            "attributes": {
              "content": "Gallery Block",
              "level": 2
            }
          }
        ]
      }
    ]
  }
}

在下面的 query 中,originalAttributes 仅获取属性,而 transformedAttributes 还将 content 属性翻译为法语:

{
  posts {
    blocks(filterBy: { include: "core/heading" } ) {
      originalAttributes: attributes
      transformedAttributes: attributes
        @underJSONObjectProperty(by: { key: "content" })
          @strTranslate(to: "fr")
    }
  }
}

然而,由于被查询的 Block 实体在 originalAttributestransformedAttributes 上引用的是同一个 JSON 对象,后者字段执行的转换也会影响前者字段(这与它们在 query 中出现的顺序无关)。

因此,两个字段都被翻译为法语:

{
  "data": {
    "posts": [
      {
        "blocks": [
          {
            "originalAttributes": {
              "content": "Bloc d'image (pleine largeur)",
              "level": 2
            },
            "transformedAttributes": {
              "content": "Bloc d'image (pleine largeur)",
              "level": 2
            }
          },
          {
            "originalAttributes": {
              "content": "Bloc Galerie",
              "level": 2
            },
            "transformedAttributes": {
              "content": "Bloc Galerie",
              "level": 2
            }
          }
        ]
      }
    ]
  }
}

通过在 transformedAttributes 字段上添加指令 @objectClone,可以避免此问题,使修改作用于克隆的 JSON 对象:

{
  posts {
    blocks(filterBy: { include: "core/heading" } ) {
      originalAttributes: attributes
      transformedAttributes: attributes
        @objectClone
        @underJSONObjectProperty(by: { key: "content" })
          @strTranslate(to: "fr")
    }
  }
}

...结果:

{
  "data": {
    "posts": [
      {
        "blocks": [
          {
            "originalAttributes": {
              "content": "Image Block (Full width)",
              "level": 2
            },
            "transformedAttributes": {
              "content": "Bloc d'image (pleine largeur)",
              "level": 2
            }
          },
          {
            "originalAttributes": {
              "content": "Gallery Block",
              "level": 2
            },
            "transformedAttributes": {
              "content": "Bloc Galerie",
              "level": 2
            }
          }
        ]
      }
    ]
  }
}

更多示例

在此 query 中,@underEachArrayItem 包裹 @underJSONObjectProperty@underJSONObjectProperty 再包裹 @strUpperCase,将通过 WP REST API 获取的多个文章条目的 "title.rendered" 属性转换为大写:

query {
  postListData: _sendJSONObjectCollectionHTTPRequest(
    url: "https://newapi.getpop.org/wp-json/wp/v2/posts/?per_page=3&_fields=id,type,title,date"
  )
    @underEachArrayItem
      @underJSONObjectProperty(by: { path: "title.rendered" })
        @strUpperCase
}

...结果:

{
  "data": {
    "postListData": [
      {
        "id": 1692,
        "date": "2022-04-26T10:10:08",
        "type": "post",
        "title": {
          "rendered": "MY BLOGROLL"
        }
      },
      {
        "id": 1657,
        "date": "2020-12-21T08:24:18",
        "type": "post",
        "title": {
          "rendered": "A TALE OF TWO CITIES – TEASER"
        }
      },
      {
        "id": 1499,
        "date": "2019-08-08T02:49:36",
        "type": "post",
        "title": {
          "rendered": "COPE WITH WORDPRESS: POST DEMO CONTAINING PLENTY OF BLOCKS"
        }
      }
    ]
  }
}