---
title: "String or List of Strings in One Pixeltable Column?"
date: "2026-07-20"
author: "Pierre Brunelle"
tags:
  - Pixeltable
  - Json
  - Array
  - Schema Design
  - list_iterator
  - Data Modeling
  - Type System
  - Computed Columns
description: "Choose pxt.Json for mixed string|list shapes, or pxt.Json[[str]] for typed string lists. Normalize with a UDF and explode rows with list_iterator."
url: "https://www.pixeltable.com/blog/pixeltable-string-or-list-columns-json-array"
---

# String or List of Strings in One Pixeltable Column?

**Summary:** Pixeltable columns are typed. There is no first-class “string *or* list of strings” union type. Use untyped `pxt.Json` when values may be either a string or a list, or typed `pxt.Json[[str]]` when every row is a uniform list of strings (and you may explode with `list_iterator`). Normalize inbound messiness with a small UDF, then use `list_iterator` when you need one row per element.

 
## The Question

 
Teams often ask: *Can I have a column that stores either a string or a list of strings?* Labels, tags, categories, and LLM outputs all arrive inconsistently—sometimes `"cat"`, sometimes `["cat", "dog"]`.

 
## Two Types, Two Jobs

 
| Type | Use when | Tradeoff |
| --- | --- | --- |
| pxt.String | Always a single string | Cannot store lists |
| pxt.Json[[str]] | Always a list of strings; plan to explode with list_iterator | Must wrap singles as ["x"] |
| pxt.Json | Shape varies (string, list, object) | Less schema rigidity; normalize for indexing / iterators |
| pxt.Array[pxt.String] | Uniform string arrays when you will not use list_iterator | list_iterator requires typed Json, not Array |

 
## Prefer Typed Json[[str]] When You Control Ingest

 
```python

import pixeltable as pxt

pxt.create_dir('tags_demo', if_exists='ignore')

t = pxt.create_table(
 'tags_demo/items',
 {
 'title': pxt.String,
 'tags': pxt.Json[[str]], # typed list of strings; works with list_iterator
 },
 if_exists='ignore',
)

t.insert([
 {'title': 'Beach walk', 'tags': ['outdoor', 'person', 'dog']},
 {'title': 'Solo portrait', 'tags': ['person']}, # single tag = list of one
])
 
```

 
Reserve `pxt.Array[...]` for numeric / embedding tensors. For tags and string lists you intend to explode, use `pxt.Json[[str]]`.

 
## Use Json When Inputs Are Messy

 
```python

raw = pxt.create_table(
 'tags_demo/raw',
 {
 'title': pxt.String,
 'tags_raw': pxt.Json, # may be str or list
 },
 if_exists='ignore',
)

raw.insert([
 {'title': 'A', 'tags_raw': 'cat'},
 {'title': 'B', 'tags_raw': ['cat', 'dog']},
])
 
```

 
### Normalize to Always-a-List

 
```python

@pxt.udf
def as_string_list(value: object) -> list[str]:
 if value is None:
 return []
 if isinstance(value, str):
 return [value]
 if isinstance(value, (list, tuple)):
 return [str(x) for x in value]
 return [str(value)]

raw.add_computed_column(
 tags=as_string_list(raw.tags_raw),
 if_exists='ignore',
)
 
```

 
Store the messy field in untyped `Json`. A UDF annotated `-> list[str]` infers a typed `Json[(String, ...)]` computed column—ready for queries and `list_iterator` (not `Array[String]`).

 
## Explode Lists with list_iterator

 
Need one row per tag for filters, joins, or aggregations? Use [`list_iterator`](https://docs.pixeltable.com/sdk/latest/json#iterator-list_iterator) (also covered in the [April 2026 release notes](/blog/pixeltable-april-2026-release-highlights)). For lists of scalars (strings, ints), pass the column as a **keyword** argument—the kwarg name becomes the output column. Positional mode is for a top-level typed `list[dict]` column only. `list_iterator` requires **typed Json** inputs—not `Array` and not untyped `Json`.

 
```python

from pixeltable.functions.json import list_iterator

# Prefer a typed Json list so the iterator can infer the output schema
typed = pxt.create_table(
 'tags_demo/typed',
 {'title': pxt.String, 'tags': pxt.Json[[str]]},
 if_exists='ignore',
)
typed.insert([
 {'title': 'Beach walk', 'tags': ['outdoor', 'person', 'dog']},
 {'title': 'Solo portrait', 'tags': ['person']},
])

tag_rows = pxt.create_view(
 'tags_demo/tag_rows',
 typed,
 iterator=list_iterator(tag=typed.tags),
 if_exists='ignore',
)

# Each element of tags becomes its own row (columns: pos, tag, …)
tag_rows.select(tag_rows.title, tag_rows.pos, tag_rows.tag).collect()
 
```

 
You can also explode the normalized computed column from the messy table: `list_iterator(tag=raw.tags)` once `raw.tags` is typed `Json[(String, ...)]`.

 
For object-detection-style outputs (a Json *object* with parallel list fields), use keyword mode to zip those fields—not a single positional call on the object:

 
```python

# Typical HF detection blob: scores / labels / boxes are parallel lists
dets = pxt.create_view(
 'tags_demo/detections',
 images,
 iterator=list_iterator(
 score=images.detections.scores,
 label=images.detections.labels,
 box=images.detections.boxes,
 ),
 if_exists='ignore',
)
 
```

 
Use positional mode only when the column itself is typed as a top-level list of dicts, e.g. `pxt.Json[[{'a': pxt.Int, 'b': pxt.String}]]` → `list_iterator(t.items)`.

 
## LLM Outputs and Embeddings

 
Chat/completion APIs often return nested JSON. Keep the raw response in `pxt.Json`, then project strings you need for embedding indexes—indexes want string (or image) columns, not arbitrary JSON blobs. See [structuring unstructured JSON LLM outputs](/blog/structure-the-unstructured-json-llm-outputs).

 
## Decision Guide

 

 - **Controlled API / app writes (and may explode later):** `Json[[str]]`, always send a list.

 - **Human CSV / partner feeds / LLM blobs:** untyped `Json` + normalize UDF → typed list Json.

 - **Analytics per element:** normalize, then `list_iterator(tag=...)` view.

 - **Semantic search on tags:** join tags into a string column, or embed each exploded row.

 - **Numeric / embedding tensors:** `pxt.Array[...]`—not for tag lists you will explode.

 

 
## FAQ

 
### Is there a String | list[String] column type?

 
No. Model it with untyped `Json`, or normalize to `Json[[str]]` / `Json[(String, ...)]`.

 
### How do I represent “no tags”?

 
Use an empty list `[]`, not `null`, for typed list columns—unless you explicitly allow nullable columns.

 
### What about list of objects?

 
Use `pxt.Json` (or typed JSON schemas / Pydantic models). See [Pydantic integration](/blog/pydantic-integration-type-safety).

 
## See Also

 

 - [Structure the unstructured: JSON LLM outputs](/blog/structure-the-unstructured-json-llm-outputs)

 - [April 2026 release: list_iterator](/blog/pixeltable-april-2026-release-highlights)

 - [Pydantic type safety with Pixeltable](/blog/pydantic-integration-type-safety)

 - [Docs: list_iterator (JSON SDK)](https://docs.pixeltable.com/sdk/latest/json#iterator-list_iterator)

 - [Docs: Iterators overview](https://docs.pixeltable.com/platform/iterators)