String or List of Strings in One Pixeltable Column?
All Stories
2026-07-204 min read
PixeltableJsonArraySchema Designlist_iteratorData ModelingType SystemComputed Columns

String or List of Strings in One Pixeltable Column?

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.

Pierre Brunelle

Pierre Brunelle

Pixeltable Team

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#

TypeUse whenTradeoff
pxt.StringAlways a single stringCannot store lists
pxt.Json[[str]]Always a list of strings; plan to explode with list_iteratorMust wrap singles as ["x"]
pxt.JsonShape varies (string, list, object)Less schema rigidity; normalize for indexing / iterators
pxt.Array[pxt.String]Uniform string arrays when you will not use list_iteratorlist_iterator requires typed Json, not Array

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

python

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

Normalize to Always-a-List#

python

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 (also covered in the April 2026 release notes). 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

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

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.

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.

See Also#

Ready to Build?

Declarative. Multimodal. Incremental.

Focus on innovation, not infrastructure.