ZhuLinsen/daily_stock_analysis · error · TypeError

json_root_not_object

Error message

json_root_not_object

What it means

Raised as TypeError('json_root_not_object') by _load_analysis_json_candidate after a candidate string parses successfully as JSON but the parsed root is not a dict (e.g. a list, a bare number, a string, null, true). The analysis contract requires a top-level JSON object, so any other root type is rejected.

Source

Thrown at src/analyzer.py:4425

    def _load_analysis_json_candidate(self, json_str: str) -> Dict[str, Any]:
        """Parse one already-selected JSON candidate, repairing common LLM JSON drift."""
        try:
            data = json.loads(json_str)
        except json.JSONDecodeError:
            stripped = (json_str or "").strip()
            try:
                _obj, end = json.JSONDecoder().raw_decode(stripped)
            except json.JSONDecodeError:
                pass
            else:
                if stripped[end:].strip():
                    raise
            if not (stripped.startswith("{") and stripped.endswith("}")):
                raise
            repaired = self._fix_json_string(stripped)
            data = json.loads(repaired)
        if not isinstance(data, dict):
            raise TypeError("json_root_not_object")
        return data

    @staticmethod
    def _contains_embedded_json_object(text: str) -> bool:
        decoder = json.JSONDecoder()
        count = 0
        for index, char in enumerate(text):
            if char != "{":
                continue
            try:
                _obj, end = decoder.raw_decode(text[index:])
            except json.JSONDecodeError:
                continue
            count += 1
            before = text[:index].strip()
            after = text[index + end:].strip()
            if count > 1 or before or after:
                return True

View on GitHub (pinned to 5159bd72e8)

Solutions

  1. Fix the prompt/contract to require a top-level JSON object with the expected keys, and show a one-shot example object.
  2. If a list is legitimately possible, wrap it at generation time ({"items": [...]}) rather than accepting arrays downstream.
  3. Inspect the raw LLM output in debug mode (--debug or the saved raw response) to see which non-dict root the model produced before changing code.
  4. If a repair path produced it, add a regression test with the exact malformed text so _fix_json_string preserves the object root.

Example fix

# before
data = analyzer._load_analysis_json_candidate(json_str)  # TypeError if root is a list

# after
parsed = json.loads(json_str)
if isinstance(parsed, list):
    parsed = {"items": parsed}
if not isinstance(parsed, dict):
    raise TypeError("json_root_not_object")
data = parsed
Defensive patterns

Strategy: validation

Validate before calling

import json

def root_is_object(json_str: str) -> bool:
    try:
        return isinstance(json.loads(json_str), dict)
    except json.JSONDecodeError:
        return False

Type guard

def is_analysis_payload(data: object) -> bool:
    return isinstance(data, dict)

Try / catch

try:
    data = analyzer._load_analysis_json_candidate(json_str)
except TypeError as exc:
    if str(exc) == 'json_root_not_object':
        parsed = json.loads(json_str)
        if isinstance(parsed, list):
            data = {"items": parsed}
        else:
            raise
    else:
        raise

Prevention

When it happens

Trigger: LLM returns a JSON array of analysis items ([{...}, {...}]) instead of an object; LLM returns a quoted string or a bare scalar ('{"summary": ...}' inside quotes, 'null', 'true', a number); a repair pass (_fix_json_string) mangles the text so the repaired result decodes to a non-dict.

Common situations: Prompt asks for 'a list of findings' and the model obliges with a top-level array; model wraps JSON in quotes thinking it should return a string; schema drift after prompt edits where the expected object shape was not re-communicated.

Related errors


AI-assisted analysis of ZhuLinsen/daily_stock_analysis@5159bd72e8 (2026-08-15). Data as JSON: /api/errors/1ec9539420c33070. Report an issue: GitHub.