Field Value Iteration and Manipulation
Addition of meta directives to the GraphQL schema, for iterating and manipulating the value elements of array and object fields:
@underArrayItem
@underJSONObjectProperty
@underEachArrayItem
@underEachJSONObjectProperty
@objectClone
@underArrayItem
@underArrayItem
makes the nested directive be applied on a specific item from the array.
In the query below, only the first item in the array with the category names is transformed to uppercase:
query {
posts {
categoryNames
@underArrayItem(index: 0)
@strUpperCase
}
}
...producing:
{
"data": {
"posts": {
"categoryNames": [
"NEWS",
"sports"
]
}
}
}
@underJSONObjectProperty
@underJSONObjectProperty
makes the nested directive receive an entry from the queried JSON object.
This directive is particularly useful to extract and manipulate a desired piece of data after querying an external API, which will quite likely have a generic JSONObject
type (as when using function field _sendJSONObjectItemHTTPRequest
from the HTTP Client extension).
In the query below, we obtain a JSON object coming from the WP REST API, and we use @underJSONObjectProperty
to manipulate the response's type
property, transforming it to upper case:
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
}
This will produce:
{
"data": {
"postData": {
"id": 1,
"date": "2019-08-02T07:53:57",
"type": "POST",
"title": {
"rendered": "Hello world!"
}
}
}
}
In addition to receiving a "key"
to point to a property that lives on the first level of the JSON object, this directive can also receive a "path"
to navigate within the inner structure of the object, using .
as a separator across levels.
In the query below, the WP REST API endpoint for a post provides property "title.rendered"
. We can navigate to that actual subelement, and transform it to title case:
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
}
This will produce:
{
"data": {
"postData": {
"id": 1,
"date": "2019-08-02T07:53:57",
"type": "post",
"title": {
"rendered": "HELLO WORLD!"
}
}
}
}
@underEachArrayItem
@underEachArrayItem
iterates over the array items from some field in the queried entity, and executes the nested directive(s) on each of them.
For instance, field Post.categoryNames
is of type [String]
. Using @underEachArrayItem
, we can iterate the category names and apply the @strTranslate
directive to them.
In this query, the post categories are translated from English to French:
query {
posts {
id
title
categoryNames
@underEachArrayItem
@strTranslate(
from: "en",
to: "fr"
)
}
}
...producing:
{
"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
can pass both the index and the value of the iterated-upon element as a dynamic variable to its nested directive(s), via directive args passIndexOnwardsAs
and passValueOnwardsAs
.
This query demonstrates the use of dynamic variables $index
and $value
:
{
_echo(value: ["first", "second", "third"])
@underEachArrayItem(
passIndexOnwardsAs: "index"
passValueOnwardsAs: "value"
)
@applyField(
name: "_echo"
arguments: {
value: {
index: $index,
value: $value
}
},
setResultInResponse: true
)
}
The result is:
{
"data": {
"_echo": [
{
"index": 0,
"value": "first"
},
{
"index": 1,
"value": "second"
},
{
"index": 2,
"value": "third"
}
]
}
}
@underEachArrayItem
can also limit the positions of the array to iterate upon, via param filter->by
, which can accept either entry include
or exclude
.
This 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
}
...produces:
{
"data": {
"including": [
"FIRST",
"second",
"THIRD"
],
"excluding": [
"first",
"SECOND",
"third"
]
}
}
@underEachJSONObjectProperty
@underEachJSONObjectProperty
is similar to @underEachArrayItem
, but operating on JSONObject
elements.
In this query, we iterate all entries in the JSON object and replace any null
entry with an empty string:
{
_echo(
value: {
first: "hello",
second: "world",
third: null
}
)
@underEachJSONObjectProperty
@default(value: "")
}
...producing:
{
"data": {
"_echo": {
"first": "hello",
"second": "world",
"third": ""
}
}
}
@underEachJSONObjectProperty
can pass the key and the value it is iterating on as a dynamic variable to its nested directive(s), via directive args passKeyOnwardsAs
and passValueOnwardsAs
.
This query demonstrates the use of dynamic variables $key
and $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
)
}
The result is:
{
"data": {
"_echo": {
"uno": {
"key": "uno",
"value": "first"
},
"dos": {
"key": "dos",
"value": "second"
},
"tres": {
"key": "tres",
"value": "third"
}
}
}
}
@underEachJSONObjectProperty
can also limit the keys from the JSON object to iterate upon, via param filter->by
, which can accept either entry includeKeys
or excludeKeys
.
This 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
}
...produces:
{
"data": {
"includingKeys": {
"uno": "FIRST",
"dos": "second",
"tres": "THIRD"
},
"excludingKeys": {
"uno": "first",
"dos": "SECOND",
"tres": "third"
}
}
}
@objectClone
JSON objects may be accessed by reference in the field resolvers (and not by copying/duplicating the object). When that is the case, when the JSON object is modified, this modification will be visible to all the fields that retrieve this JSON object.
This is the case with field Block.attributes
:
{
posts {
blocks(filterBy: { include: "core/heading" } ) {
attributes
}
}
}
...which produces:
{
"data": {
"posts": [
{
"blocks": [
{
"attributes": {
"content": "Image Block (Full width)",
"level": 2
}
},
{
"attributes": {
"content": "Gallery Block",
"level": 2
}
}
]
}
]
}
}
In the query below, while originalAttributes
is simply retrieving the attributes, transformedAttributes
will also translate the content
property to French:
{
posts {
blocks(filterBy: { include: "core/heading" } ) {
originalAttributes: attributes
transformedAttributes: attributes
@underJSONObjectProperty(by: { key: "content" })
@strTranslate(to: "fr")
}
}
}
However, as the queried Block
entity references the same JSON object on both originalAttributes
and transformedAttributes
, the transformations performed by the latter field will also affect the former field (this is irrespective of the order in which they appear in the query).
As a result, both fields are translated to French:
{
"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
}
}
]
}
]
}
}
We can avoid this issue by adding directive @objectClone
on the transformedAttributes
field, so that the modifications are performed on a cloned JSON object:
{
posts {
blocks(filterBy: { include: "core/heading" } ) {
originalAttributes: attributes
transformedAttributes: attributes
@objectClone
@underJSONObjectProperty(by: { key: "content" })
@strTranslate(to: "fr")
}
}
}
...producing:
{
"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
}
}
]
}
]
}
}
Further examples
In this query, @underEachArrayItem
wraps @underJSONObjectProperty
, which itself wraps @strUpperCase
, transforming the "title.rendered"
property into upper case, for the multiple post entries obtained via the WP REST API:
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
}
...producing:
{
"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"
}
}
]
}
}