Query Functions
Query FunctionsItération et Manipulation de la Valeur des Champs

Itération et Manipulation de la Valeur des Champs

Included in the “Power Extensions” bundle

Ajout de méta-directives au schéma GraphQL, pour itérer et manipuler les éléments de la valeur des champs de type tableau et objet :

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

@underArrayItem

@underArrayItem fait en sorte que la directive imbriquée soit appliquée sur un élément spécifique du tableau.

Dans la requête ci-dessous, seul le premier élément du tableau contenant les noms de catégories est transformé en majuscules :

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

...produisant :

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

@underJSONObjectProperty

@underJSONObjectProperty fait en sorte que la directive imbriquée reçoive une entrée de l'objet JSON interrogé.

Cette directive est particulièrement utile pour extraire et manipuler une donnée souhaitée après avoir interrogé une API externe, qui aura très probablement un type générique JSONObject (comme lorsqu'on utilise le champ de fonction _sendJSONObjectItemHTTPRequest de l'extension HTTP Client).

Dans la requête ci-dessous, nous obtenons un objet JSON provenant de l'API REST WP, et nous utilisons @underJSONObjectProperty pour manipuler la propriété type de la réponse, en la transformant en majuscules :

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
}

Cela produira :

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

En plus de recevoir une "key" pour pointer vers une propriété qui se trouve au premier niveau de l'objet JSON, cette directive peut également recevoir un "path" pour naviguer dans la structure interne de l'objet, en utilisant . comme séparateur entre les niveaux.

Dans la requête ci-dessous, l'endpoint de l'API REST WP pour un article fournit la propriété "title.rendered". Nous pouvons naviguer jusqu'à ce sous-élément réel, et le transformer en casse de titre :

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
}

Cela produira :

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

@underEachArrayItem

@underEachArrayItem itère sur les éléments du tableau d'un certain champ de l'entité interrogée, et exécute la ou les directives imbriquées sur chacun d'eux.

Par exemple, le champ Post.categoryNames est de type [String]. En utilisant @underEachArrayItem, nous pouvons itérer les noms de catégories et leur appliquer la directive @strTranslate.

Dans cette requête, les catégories de l'article sont traduites de l'anglais vers le français :

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

...produisant :

{
  "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 peut passer à la fois l'index et la valeur de l'élément itéré comme variable dynamique à sa ou ses directives imbriquées, via les args de directive passIndexOnwardsAs et passValueOnwardsAs.

Cette requête démontre l'utilisation des variables dynamiques $index et $value :

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

Le résultat est :

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

@underEachArrayItem peut également limiter les positions du tableau sur lesquelles itérer, via le param filter->by, qui peut accepter soit l'entrée include soit exclude.

Cette requête :

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

...produit :

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

@underEachJSONObjectProperty

@underEachJSONObjectProperty est similaire à @underEachArrayItem, mais opère sur des éléments JSONObject.

Dans cette requête, nous itérons toutes les entrées de l'objet JSON et remplaçons toute entrée null par une chaîne vide :

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

...produisant :

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

@underEachJSONObjectProperty peut passer la clé et la valeur sur lesquelles il itère comme variable dynamique à sa ou ses directives imbriquées, via les args de directive passKeyOnwardsAs et passValueOnwardsAs.

Cette requête démontre l'utilisation des variables dynamiques $key et $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
      )
}

Le résultat est :

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

@underEachJSONObjectProperty peut également limiter les clés de l'objet JSON sur lesquelles itérer, via le param filter->by, qui peut accepter soit l'entrée includeKeys soit excludeKeys.

Cette requête :

{
  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
}

...produit :

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

@objectClone

Les objets JSON peuvent être accédés par référence dans les résolveurs de champs (et non par copie/duplication de l'objet). Dans ce cas, lorsque l'objet JSON est modifié, cette modification sera visible pour tous les champs qui récupèrent cet objet JSON.

C'est le cas avec le champ Block.attributes :

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

...qui produit :

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

Dans la requête ci-dessous, tandis que originalAttributes récupère simplement les attributs, transformedAttributes traduira également la propriété content en français :

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

Cependant, comme l'entité Block interrogée référence le même objet JSON à la fois dans originalAttributes et transformedAttributes, les transformations effectuées par ce dernier champ affecteront également le premier (cela est indépendant de l'ordre dans lequel ils apparaissent dans la requête).

Par conséquent, les deux champs sont traduits en français :

{
  "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
            }
          }
        ]
      }
    ]
  }
}

Nous pouvons éviter ce problème en ajoutant la directive @objectClone sur le champ transformedAttributes, afin que les modifications soient effectuées sur un objet JSON cloné :

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

...produisant :

{
  "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
            }
          }
        ]
      }
    ]
  }
}

Autres exemples

Dans cette requête, @underEachArrayItem enveloppe @underJSONObjectProperty, qui lui-même enveloppe @strUpperCase, transformant la propriété "title.rendered" en majuscules, pour les multiples entrées d'articles obtenues via l'API REST WP :

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
}

...produisant :

{
  "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"
        }
      }
    ]
  }
}