deepset-ai/haystack · error · ValueError
Input must be a dictionary or a list of dictionaries.
Error message
Input must be a dictionary or a list of dictionaries.
What it means
JsonSchemaValidator's internal _recursive_json_to_object recursively converts JSON strings embedded in dict values into objects. It only accepts a dict or a list at the top level (dicts and lists are recursed; scalars inside dicts are preserved). Passing any other top-level value (str, int, None, etc.) hits the terminal raise ValueError.
Source
Thrown at haystack/components/validators/json_schema.py:252
new_dict = {}
for key, value in data.items():
if isinstance(value, str):
try:
json_value = json.loads(value)
if isinstance(json_value, (dict, list)):
new_dict[key] = self._recursive_json_to_object(json_value)
else:
new_dict[key] = value # Preserve the original string value
except json.JSONDecodeError:
new_dict[key] = value
elif isinstance(value, dict):
new_dict[key] = self._recursive_json_to_object(value)
else:
new_dict[key] = value
return new_dict
# If it's neither a list nor a dictionary, return the value directly
raise ValueError("Input must be a dictionary or a list of dictionaries.")
View on GitHub (pinned to e318778c9b)
Solutions
- Parse the input before validation: if isinstance(data, str): data = json.loads(data) before calling JsonSchemaValidator.run.
- Ensure the LLM output is converted to dict/list (e.g. via an output adapter or JSONOutputParser) before the validator.
- If data may be None (e.g. failed generation), check and provide a fallback dict before run().
- Check that double-encoded JSON isn't the issue: json.loads may need to be applied twice for nested stringified JSON.
Example fix
// before
result = validator.run(json=llm_output_text)
// after
if isinstance(llm_output_text, str):
llm_output_text = json.loads(llm_output_text)
result = validator.run(json=llm_output_text) Defensive patterns
Strategy: validation
Validate before calling
def is_valid_validator_input(data) -> bool:
return isinstance(data, (dict, list)) and not (isinstance(data, list) and any(not isinstance(i, dict) for i in data))
# guard: if isinstance(data, str): data = json.loads(data) Type guard
def is_dict_or_dict_list(data: Any) -> TypeGuard[dict | list[dict]]:
if isinstance(data, dict):
return True
return isinstance(data, list) and all(isinstance(i, dict) for i in data) Try / catch
try:
result = validator.run(json=data)
except ValueError as e:
if "Input must be a dictionary" in str(e):
data = json.loads(data) if isinstance(data, str) else {}
result = validator.run(json=data) Prevention
- Always json.loads LLM string output before JsonSchemaValidator
- Check for None from upstream components before validation
- Beware double-encoded JSON strings (loads may need two passes)
- Type-annotate pipeline connections so non-dict outputs can't be wired to the validator
When it happens
Trigger: Calling JsonSchemaValidator.run with a `json` input that is a bare JSON string, a scalar, or None, instead of a dict or list of dicts. This happens when an upstream LLM component emits the raw string rather than a parsed dict, or when a template/wire step yields None.
Common situations: LLM completion text not parsed to JSON before validation; a generator/extractor component outputs a string; pipeline wiring passes a ChatMessage content string directly; JSON string was json.loads'd at one level but the top-level value remained a string (double-encoded JSON).
Related errors
- The following tool names are not valid: {invalid_tool_names}
- user_prompt must define exactly one message block, found {le
- system_prompt must define exactly one message block, found {
- Number of replies ({len(replies)}), and metadata ({len(meta)
- Pattern '{pattern}' contains multiple capture groups. Please
AI-assisted analysis of deepset-ai/haystack@e318778c9b (2026-08-30).
Data as JSON: /api/errors/bcac767de2f3da1f.
Report an issue: GitHub.