{"record":{"id":"93fb9cb0d9bcb2e8","repo":"ZhuLinsen/daily_stock_analysis","slug":"event-alert-rules-must-be-a-json-array","errorCode":null,"errorMessage":"Event alert rules must be a JSON array","messagePattern":"Event alert rules must be a JSON array","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/agent/events.py","lineNumber":451,"sourceCode":"\n\ndef parse_event_alert_rules(raw_rules: Any) -> List[Dict[str, Any]]:\n    \"\"\"Parse event alert rules from config JSON or already-loaded objects.\"\"\"\n    if raw_rules is None:\n        return []\n\n    parsed = raw_rules\n    if isinstance(raw_rules, str):\n        cleaned = raw_rules.strip()\n        if not cleaned:\n            return []\n        parsed = json.loads(cleaned)\n\n    if isinstance(parsed, dict):\n        parsed = parsed.get(\"rules\", [])\n\n    if not isinstance(parsed, list):\n        raise ValueError(\"Event alert rules must be a JSON array\")\n\n    invalid_indices = [idx for idx, entry in enumerate(parsed) if not isinstance(entry, dict)]\n    if invalid_indices:\n        raise ValueError(\n            \"Event alert rules list must contain only objects; \"\n            f\"invalid entries at positions: {invalid_indices}\"\n        )\n\n    return parsed\n\n\ndef validate_event_alert_rule(rule: Dict[str, Any]) -> None:\n    \"\"\"Validate one serialized EventMonitor rule.\"\"\"\n    if not isinstance(rule, dict):\n        raise ValueError(\"Event alert rule must be an object\")\n\n    stock_code = str(rule.get(\"stock_code\") or \"\").strip()\n    if not stock_code:","sourceCodeStart":433,"sourceCodeEnd":469,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/src/agent/events.py#L433-L469","documentation":"The rules-parsing helper accepts raw rules as: a JSON string (parsed with json.loads, empty string -> []), a dict (unwrapped via its 'rules' key), or an already-parsed list. After that, anything that is still not a list — a bare scalar, a JSON string encoding an object without 'rules', a top-level JSON number/bool — raises ValueError('Event alert rules must be a JSON array').","triggerScenarios":"Passing raw_rules='42', 'true', '\"alert\"', a dict without a 'rules' key, or a list-of-lists-free scalar to the parser; also JSON text whose top level is not an array or a {\"rules\": [...]} wrapper.","commonSituations":"API endpoints forwarding arbitrary user JSON; env vars holding malformed rule blobs; frontend sending an object keyed by stock code instead of an array; trailing commas making json.loads return a non-container.","solutions":["Ensure the payload's top level is an array of rule objects: [{...}, {...}]","If you send an object, it must be exactly {\"rules\": [...]} — the parser unwraps only that key","Validate with the parser's own contract (call it inside try/except) before persisting, and return a 400 to the client on ValueError","Log the offending payload shape (not contents) to catch producer-side format drift"],"exampleFix":"// before\nraw = '{\"600519\": {\"alert_type\": \"price_cross\"}}'  # dict without \"rules\"\nparse_rules(raw)  # ValueError\n\n// after\nraw = '{\"rules\": [{\"stock_code\": \"600519\", \"alert_type\": \"price_cross\", \"price\": 1800}]}'","handlingStrategy":"validation","validationCode":"import json\n\ndef rules_payload_ok(raw) -> bool:\n    parsed = json.loads(raw) if isinstance(raw, str) else raw\n    if isinstance(parsed, dict):\n        parsed = parsed.get(\"rules\")\n    return isinstance(parsed, list)","typeGuard":"import json\n\ndef is_rules_array(raw) -> bool:\n    try:\n        parsed = json.loads(raw) if isinstance(raw, str) else raw\n    except json.JSONDecodeError:\n        return False\n    if isinstance(parsed, dict):\n        parsed = parsed.get(\"rules\")\n    return isinstance(parsed, list)","tryCatchPattern":"except ValueError as exc:\n    if \"must be a JSON array\" in str(exc):\n        return HTTP400(f\"rules must be a JSON array: {exc}\")","preventionTips":["Make producers emit arrays of objects","Reject bad shapes at the API boundary","Unit-test the parser against scalars and wrappers"],"tags":["python","json","validation","alerts"],"backgroundTag":null,"analyzedSha":"5159bd72e8373d215492dff122acc9d389e219c9","analyzedAt":"2026-08-15T01:59:36.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}