---
title: "keys Syntax Reference"
slug: keys-syntax
type: reference
plugin: huxx-connect
summary: "Formal grammar and resolution rules for the keys extraction syntax: dot paths, brace lists, the [] wildcard, and every edge case."
keywords: [keys, syntax, grammar, dot notation, wildcard, reference]
version_added: 1.2.5
version_updated: 1.2.5
level: advanced
related: [extract-values-with-keys, shortcodes, template-functions]
---

Worked examples: [Extract Values with the keys Syntax](/docs/connect/how-to/extract-values-with-keys/).

The syntax is implemented by `HuxxConnectCoreArrayKeyExtractor` (`src/Core/ArrayKeyExtractor.php`): `parse()` turns a keys string into path(s), `extract()` resolves path(s) against the decoded response array. The shortcode `keys` attribute and the template functions' `keys` argument (string form) both go through `parse()`; an array passed to the `keys` argument goes straight to `extract()`.

## Grammar

```
keys        = brace-list | bare-list | bare-path
brace-list  = "{" path "}" *( "," "{" path "}" )
bare-list   = path 1*( "," path )
bare-path   = path
path        = segment *( "." segment )
segment     = key | index | "[]"
key         = string array key   ; any characters except "." (and "}" inside braces, "," in bare lists)
index       = numeric list position, zero-based
"[]"        = wildcard: fan out across every item of a sequential list
```

There is no escaping mechanism: a response key that itself contains `.`, `,`, `{`, or `}` cannot be addressed.

## Parsing rules (`parse()`)

Applied to the raw string in order:

1. Leading/trailing whitespace is trimmed; an empty string parses to `""` (extraction is skipped by the shortcode; `extract()` returns `null` for it).
2. **Brace notation** — if the string contains `{`, all `{…}` groups are captured (`/{([^}]+)}/`), each trimmed. One group → single path (string). Multiple groups → list of paths. Text outside braces is ignored. Empty braces `{}` or a `{` with no well-formed group fall through to bare handling.
3. **Bare notation with commas** — the string splits on `,`; each piece is trimmed and becomes a path.
4. **Bare single path** — anything else is one path.

| Input | Parses to |
|---|---|
| `products.0.title` | `"products.0.title"` |
| `{products.0.title}` | `"products.0.title"` |
| `{title},{price}` | `["title", "price"]` |
| `title, price` | `["title", "price"]` |
| `{a.b}, ignored, {c}` | `["a.b", "c"]` — non-brace text ignored once braces are present |
| `{}` | falls through to bare: `"{}"` (resolves to `null`) |
| `""` | `""` |

## Resolution rules (`extract()`)

Input forms:

| `$keys` form | Result |
|---|---|
| string path | The value at that path, or `null` if not found. |
| array, 1 entry | Same as a string path — unwrapped raw value. |
| array, 2+ entries | Associative array keyed by each **full dot path**; paths resolving to `null` are omitted. |
| empty array (or non-array, non-string) | The source data, unchanged. |

Traversal (paths without `[]`):

- Each segment indexes into the current array with `isset()`. A missing key returns `null` for the whole path.
- Because `isset()` is used, a key whose value is literally `null` is indistinguishable from a missing key — the path resolves to `null` either way, and is omitted from multi-key results.
- Numeric segments address list positions (`products.0` = first item). No negative indexes, no ranges.
- Traversal stops with `null` if an intermediate value is a scalar (you cannot descend into a string or number).

Wildcard (`[]`) resolution:

- A `[]` segment fans out across every element of the current context. Each context must be a **sequential list** (keys `0..n-1`; the empty array counts as sequential) — non-list contexts are skipped.
- Remaining segments resolve against every fanned-out element; elements missing the segment are dropped.
- If any step leaves zero surviving contexts, the whole path resolves to `null`.
- Multiple wildcards nest: `products.[].reviews.[].rating` collects every rating of every product.
- Final results are flattened one level: surviving values that are themselves sequential lists are merged item-by-item into the single result list; other values are appended as-is.

## Examples

Against the DummyJSON demo response (GET `https://dummyjson.com/products`, wrapped list root `products`):

| keys | Result |
|---|---|
| `total` | `194` |
| `products.0.title` | `"Essence Mascara Lash Princess"` |
| `{products.0.title},{products.0.price}` | `{"products.0.title": "Essence Mascara Lash Princess", "products.0.price": 9.99}` |
| `products.[].title` | `["Essence Mascara Lash Princess", "Eyeshadow Palette with Mirror", …]` |
| `products.[].reviews.[].rating` | `[3, 4, 5, …]` — flattened across all products |
| `products.99999.title` | `null` |
| `[].title` | `null` — the response root is keyed, not a list |

Runnable example:

```php
<?php
$titles = huxx_api( 'demo_api', 'get_products', array( 'keys' => 'products.[].title' ) );
```

Output (truncated):

```json
["Essence Mascara Lash Princess", "Eyeshadow Palette with Mirror"]
```

> **Warning:** the `[]` wildcard cannot be used inside a shortcode attribute. WordPress terminates a shortcode at the first `]` it finds, so `keys="products.[].title"` cuts the tag off mid-attribute and the keys path is never applied. Wildcard paths work only through the PHP `huxx_api()` function; all other keys forms work in both the shortcode and PHP.

## Edge cases

| Case | Behavior |
|---|---|
| Path not found | `null` (single path); omitted (multi-key). |
| Value is literally `null` | Treated as not found. |
| Non-array response (`JSON String` / `Raw` results, or after a scalar extraction) | Extraction is skipped by both the shortcode and template functions — `keys` only applies to arrays. |
| `[]` on an associative array | That context is skipped; if no list contexts remain, the path is `null`. |
| `[]` on an empty list | No surviving contexts → `null`. |
| Whitespace around paths or inside braces | Trimmed. |
| Duplicate paths in a multi-key list | Collapse to one entry (results are keyed by path). |
| Key containing `.` in the response | Not addressable — the dot always splits segments. |
