{"record":{"id":"26e04eeedb6516ca","repo":"deepset-ai/haystack","slug":"meta-must-be-either-none-a-dictionary-or-a-list-o","errorCode":null,"errorMessage":"meta must be either None, a dictionary or a list of dictionaries.","messagePattern":"meta must be either None, a dictionary or a list of dictionaries\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"haystack/components/converters/utils.py","lineNumber":82,"sourceCode":"    makes sure to return a list of dictionaries of the correct length for the converter to use.\n\n    :param meta: the meta input of the converter, as-is\n    :param sources_count: the number of sources the converter received\n    :returns: a list of dictionaries of the make length as the sources list\n\n    Each source always gets its own independent dictionary. When ``meta`` is ``None`` or a single\n    dictionary, a separate copy is returned for every source so that mutating one source's metadata\n    downstream does not leak into the others.\n    \"\"\"\n    if meta is None:\n        return [{} for _ in range(sources_count)]\n    if isinstance(meta, dict):\n        return [deepcopy(meta) for _ in range(sources_count)]\n    if isinstance(meta, list):\n        if sources_count != len(meta):\n            raise ValueError(\"The length of the metadata list must match the number of sources.\")\n        return meta\n    raise ValueError(\"meta must be either None, a dictionary or a list of dictionaries.\")\n","sourceCodeStart":64,"sourceCodeEnd":83,"githubUrl":"https://github.com/deepset-ai/haystack/blob/e318778c9bf60a1963e3b5f451359655dd696c30/haystack/components/converters/utils.py#L64-L83","documentation":"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.","triggerScenarios":"Calling a converter's run(..., meta=...) with a non-dict/list/None value such as a string, a tuple of dicts, or a generator.","commonSituations":"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.","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."],"exampleFix":"# before\nconverter.run(sources=srcs, meta='{\"author\": \"x\"}')  # str\n# after\nimport json\nconverter.run(sources=srcs, meta=json.loads('{\"author\": \"x\"}'))","handlingStrategy":"type-guard","validationCode":"def is_valid_meta(meta) -> bool:\n    return meta is None or isinstance(meta, dict) or (\n        isinstance(meta, list) and all(isinstance(m, dict) for m in meta)\n    )\n\nassert is_valid_meta(meta), f\"meta must be None/dict/list-of-dicts, got {type(meta)}\"","typeGuard":"def is_meta(meta: object) -> bool:\n    if meta is None or isinstance(meta, dict):\n        return True\n    return isinstance(meta, list) and all(isinstance(m, dict) for m in meta)","tryCatchPattern":"try:\n    result = converter.run(sources=sources, meta=meta)\nexcept ValueError as e:\n    if \"meta must be\" in str(e):\n        meta = dict(json.loads(meta)) if isinstance(meta, str) else None\n        result = converter.run(sources=sources, meta=meta)\n    else:\n        raise","preventionTips":["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"],"tags":["python","valueerror","metadata","type-error"],"backgroundTag":"invalid-argument-type","analyzedSha":"e318778c9bf60a1963e3b5f451359655dd696c30","analyzedAt":"2026-08-30T11:45:20.711Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}