deepset-ai/haystack · error
meta must be either None, a dictionary or a list of dictiona
Error message
meta must be either None, a dictionary or a list of dictionaries.
What it means
normalize_metadata only accepts None, a single dict, or a list of dicts as meta. Any other type (str, tuple, set, nested list, etc.) raises ValueError. This validates the type of the metadata argument before converters attach it to documents.
Source
Thrown at haystack/components/converters/utils.py:82
makes sure to return a list of dictionaries of the correct length for the converter to use.
:param meta: the meta input of the converter, as-is
:param sources_count: the number of sources the converter received
:returns: a list of dictionaries of the make length as the sources list
Each source always gets its own independent dictionary. When ``meta`` is ``None`` or a single
dictionary, a separate copy is returned for every source so that mutating one source's metadata
downstream does not leak into the others.
"""
if meta is None:
return [{} for _ in range(sources_count)]
if isinstance(meta, dict):
return [deepcopy(meta) for _ in range(sources_count)]
if isinstance(meta, list):
if sources_count != len(meta):
raise ValueError("The length of the metadata list must match the number of sources.")
return meta
raise ValueError("meta must be either None, a dictionary or a list of dictionaries.")
View on GitHub (pinned to e318778c9b)
Solutions
- Convert the input to a plain dict (json.loads for JSON strings) or list of dicts.
- Wrap a tuple/iterator in list(...) and each item in dict(...).
- Pass None if no metadata is needed.
Example fix
# before
converter.run(sources=srcs, meta='{"author": "x"}') # str
# after
import json
converter.run(sources=srcs, meta=json.loads('{"author": "x"}')) Defensive patterns
Strategy: type-guard
Validate before calling
def is_valid_meta(meta) -> bool:
return meta is None or isinstance(meta, dict) or (
isinstance(meta, list) and all(isinstance(m, dict) for m in meta)
)
assert is_valid_meta(meta), f"meta must be None/dict/list-of-dicts, got {type(meta)}" Type guard
def is_meta(meta: object) -> bool:
if meta is None or isinstance(meta, dict):
return True
return isinstance(meta, list) and all(isinstance(m, dict) for m in meta) Try / catch
try:
result = converter.run(sources=sources, meta=meta)
except ValueError as e:
if "meta must be" in str(e):
meta = dict(json.loads(meta)) if isinstance(meta, str) else None
result = converter.run(sources=sources, meta=meta)
else:
raise Prevention
- Parse JSON metadata strings with json.loads before passing them
- Coerce tuples/sets/generators to list of dicts at ingestion time
- Annotate the meta parameter as dict | list[dict] | None in your pipeline code so type checkers catch mistakes
When it happens
Trigger: Calling a converter's run(..., meta=...) with a non-dict/list/None value such as a string, a tuple of dicts, or a generator.
Common situations: Passing a JSON string of metadata instead of a parsed dict; passing a tuple or pandas-derived structure; forgetting to deserialize metadata loaded from a file.
Understand the failure class
Background: Invalid argument type errors: "must be of type string", "expected X, got Y", and ERR_INVALID_ARG_TYPE explained — this error's family across 15 libraries.
Related errors
- Unsupported source type {type(source)}
- The length of the metadata list must match the number of sou
- CSVToDocument: quotechar must be a single character.
- Unknown table format '{string}'. Supported formats are: {lis
- Document with ID '{doc.id}' is missing the '{file_path_meta_
AI-assisted analysis of deepset-ai/haystack@e318778c9b (2026-08-30).
Data as JSON: /api/errors/26e04eeedb6516ca.
Report an issue: GitHub.