---
title: "Union nested arrays with key"
slug: "union-nested-arrays-with-key"
description: "Transform nested arrays into a flat report with category tags using the union_nested_arrays_with_key adapter. Control inclusion with mode and keys."
updated: 2026-06-04T07:44:41Z
published: 2026-06-04T07:44:41Z
canonical: "docs.supermetrics.com/union-nested-arrays-with-key"
---

> ## Documentation Index
> Fetch the complete documentation index at: https://docs.supermetrics.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Union nested arrays with key

For each parent item under `dataPath`, the `union_nested_arrays_with_key` adapter looks up the object at `containerPath`, treats each of its child keys as a category, and emits one row per entry found inside each category's array. Each emitted row records its source category name under `keyName`, and the parent item's other (non-container) fields are preserved on every row. Use `mode` + `keys` to whitelist or denylist which categories to include.

Use this when an API returns per-category arrays nested under a single object (for example, one array per channel under a `messages` object) and you want a single flat report tagged with the category name. Stacking `flatten_values` once per category instead produces a cartesian product across categories.

| Field | Type | Default | Required | Description |
| --- | --- | --- | --- | --- |
| `dataPath` | string | `$.*` | Yes | JSONPath to the iterable of parent items. The transformed list is written back under this path. |
| `containerPath` | string | – | Yes | JSONPath, relative to each parent item, pointing at the container whose children will be unioned. |
| `keyName` | string | – | Yes | Property name under which each emitted row records its source category key. |
| `mode` | string | `include` | No | One of `include` or `exclude`. With `include`, `keys` is a whitelist; with `exclude`, `keys` is a denylist. |
| `keys` | array | `[]` | No | Category keys to include or exclude depending on `mode`. May be empty for `include` (unions every category); must be non-empty for `exclude`. |

The combinations of `mode` and `keys` resolve as follows:

| `mode` | `keys` | Behavior |
| --- | --- | --- |
| `include` | `[]` | Default — union every category in the container. |
| `include` | non-empty | Whitelist — union only the listed categories. |
| `exclude` | non-empty | Denylist — union every category except the listed ones. |
| `exclude` | `[]` | Invalid — rejected by the configuration validator. |

> **Note:** Non-list siblings of the container (for example, a scalar or object alongside the per-category arrays) are silently skipped — they do not produce rows and do not raise errors.

Below you can find a before/after example. The original response groups variations per channel:

```
{
  "data": [
    {
      "time": "2024-01-01T00:00:00Z",
      "messages": {
        "email": [
          {
            "variation_name": "v1",
            "sent": 100
          },
          {
            "variation_name": "v2",
            "sent": 80
          }
        ],
        "push": [
          {
            "variation_name": "v1",
            "sent": 50
          }
        ]
      }
    }
  ]
}
```

After the adapter runs, each variation becomes its own row with the channel tagged on it:

```
{
  "data": [
    {
      "time": "2024-01-01T00:00:00Z",
      "channel_type": "email",
      "variation_name": "v1",
      "sent": 100
    },
    {
      "time": "2024-01-01T00:00:00Z",
      "channel_type": "email",
      "variation_name": "v2",
      "sent": 80
    },
    {
      "time": "2024-01-01T00:00:00Z",
      "channel_type": "push",
      "variation_name": "v1",
      "sent": 50
    }
  ]
}
```

Configuration:

```
"response": {
  "dataRows": {
    "source": "jsonPath",
    "value": "$.data.*"
  },
  "dataAdapters": {
    "items": [
      {
        "type": "union_nested_arrays_with_key",
        "config": {
          "dataPath": "$.data.*",
          "containerPath": "$.messages",
          "keyName": "channel_type"
        }
      }
    ]
  }
}
```

To include only specific categories, set `mode: "include"` and list them in `keys`:

```
{
  "type": "union_nested_arrays_with_key",
  "config": {
    "dataPath": "$.data.*",
    "containerPath": "$.messages",
    "mode": "include",
    "keys": [
      "email",
      "push"
    ],
    "keyName": "channel_type"
  }
}
```

To exclude specific categories instead, set `mode: "exclude"` and list the keys to skip:

```
{
  "type": "union_nested_arrays_with_key",
  "config": {
    "dataPath": "$.data.*",
    "containerPath": "$.messages",
    "mode": "exclude",
    "keys": [
      "summary",
      "metadata"
    ],
    "keyName": "channel_type"
  }
}
```

Below you can find example field definitions that read from the transformed structure:

```
[
  {
    "id": "time",
    "label": "Time",
    "dataType": "string.time.date_time",
    "value": {
      "source": "jsonPath",
      "value": "$.time"
    }
  },
  {
    "id": "channel_type",
    "label": "Channel type",
    "dataType": "string.text.value",
    "value": {
      "source": "jsonPath",
      "value": "$.channel_type"
    }
  },
  {
    "id": "variation_name",
    "label": "Variation name",
    "dataType": "string.text.value",
    "value": {
      "source": "jsonPath",
      "value": "$.variation_name"
    }
  },
  {
    "id": "sent",
    "label": "Sent",
    "dataType": "int.number.value",
    "value": {
      "source": "jsonPath",
      "value": "$.sent"
    }
  }
]
```
