Query Functions
Query FunctionsIteración y Manipulación del Valor de Campos

Iteración y Manipulación del Valor de Campos

Included in the “Power Extensions” bundle

Adición de meta-directivas al esquema GraphQL, para iterar y manipular los elementos del valor de los campos de tipo array y object:

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

@underArrayItem

@underArrayItem hace que la directiva anidada se aplique sobre un elemento específico del array.

En la consulta de abajo, solo el primer elemento del array con los nombres de las categorías se transforma a mayúsculas:

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

...produciendo:

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

@underJSONObjectProperty

@underJSONObjectProperty hace que la directiva anidada reciba una entrada del objeto JSON consultado.

Esta directiva es particularmente útil para extraer y manipular una pieza de datos deseada tras consultar una API externa, que muy probablemente tendrá un tipo genérico JSONObject (como cuando se usa el campo de función _sendJSONObjectItemHTTPRequest de la extensión HTTP Client).

En la consulta de abajo, obtenemos un objeto JSON proveniente de la API REST de WP, y usamos @underJSONObjectProperty para manipular la propiedad type de la respuesta, transformándola a mayúsculas:

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
}

Esto producirá:

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

Además de recibir una "key" para apuntar a una propiedad que vive en el primer nivel del objeto JSON, esta directiva también puede recibir una "path" para navegar dentro de la estructura interna del objeto, usando . como separador entre niveles.

En la consulta de abajo, el endpoint de la API REST de WP para una entrada proporciona la propiedad "title.rendered". Podemos navegar a ese subelemento real, y transformarlo a capitalización de título:

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
}

Esto producirá:

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

@underEachArrayItem

@underEachArrayItem itera sobre los elementos del array de algún campo de la entidad consultada, y ejecuta la(s) directiva(s) anidada(s) sobre cada uno de ellos.

Por ejemplo, el campo Post.categoryNames es de tipo [String]. Usando @underEachArrayItem, podemos iterar los nombres de las categorías y aplicarles la directiva @strTranslate.

En esta consulta, las categorías de la entrada se traducen del inglés al francés:

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

...produciendo:

{
  "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 puede pasar tanto el índice como el valor del elemento iterado como variable dinámica a su(s) directiva(s) anidada(s), mediante los args de directiva passIndexOnwardsAs y passValueOnwardsAs.

Esta consulta demuestra el uso de las variables dinámicas $index y $value:

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

El resultado es:

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

@underEachArrayItem también puede limitar las posiciones del array sobre las que iterar, mediante el param filter->by, que puede aceptar bien la entrada include o exclude.

Esta consulta:

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

...produce:

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

@underEachJSONObjectProperty

@underEachJSONObjectProperty es similar a @underEachArrayItem, pero operando sobre elementos JSONObject.

En esta consulta, iteramos todas las entradas del objeto JSON y reemplazamos cualquier entrada null con un string vacío:

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

...produciendo:

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

@underEachJSONObjectProperty puede pasar la clave y el valor sobre el que está iterando como variable dinámica a su(s) directiva(s) anidada(s), mediante los args de directiva passKeyOnwardsAs y passValueOnwardsAs.

Esta consulta demuestra el uso de las variables dinámicas $key y $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
      )
}

El resultado es:

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

@underEachJSONObjectProperty también puede limitar las claves del objeto JSON sobre las que iterar, mediante el param filter->by, que puede aceptar bien la entrada includeKeys o excludeKeys.

Esta consulta:

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

...produce:

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

@objectClone

Los objetos JSON pueden ser accedidos por referencia en los resolvers de campos (y no copiando/duplicando el objeto). Cuando es así, cuando el objeto JSON se modifica, esa modificación será visible para todos los campos que recuperen ese objeto JSON.

Este es el caso con el campo Block.attributes:

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

...que produce:

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

En la consulta de abajo, mientras que originalAttributes simplemente está recuperando los atributos, transformedAttributes también traducirá la propiedad content al francés:

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

Sin embargo, como la entidad Block consultada referencia el mismo objeto JSON tanto en originalAttributes como en transformedAttributes, las transformaciones realizadas por este último campo también afectarán al primero (esto es independiente del orden en que aparezcan en la consulta).

Como resultado, ambos campos se traducen al francés:

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

Podemos evitar este problema añadiendo la directiva @objectClone en el campo transformedAttributes, de modo que las modificaciones se realicen sobre un objeto JSON clonado:

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

...produciendo:

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

Más ejemplos

En esta consulta, @underEachArrayItem envuelve a @underJSONObjectProperty, que a su vez envuelve a @strUpperCase, transformando la propiedad "title.rendered" a mayúsculas, para las múltiples entradas de post obtenidas mediante la API REST de 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
}

...produciendo:

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