{"record":{"id":"ddc6c8c78f3e3f14","repo":"opendatalab/MinerU","slug":"payload-name-must-be-a-json-object","errorCode":null,"errorMessage":"{payload_name} must be a JSON object","messagePattern":"(.+?) must be a JSON object","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"mineru/cli/router.py","lineNumber":111,"sourceCode":"    try:\n        value = int(os.getenv(name, str(default)))\n    except ValueError:\n        return default\n    if value < minimum:\n        return default\n    return value\n\n\ndef _parse_json_object_response(\n    response: httpx.Response,\n    payload_name: str,\n) -> dict[str, Any]:\n    try:\n        payload = response.json()\n    except ValueError as exc:\n        raise ValueError(f\"{payload_name} is not valid JSON\") from exc\n    if not isinstance(payload, dict):\n        raise ValueError(f\"{payload_name} must be a JSON object\")\n    return payload\n\n\ndef get_task_retention_seconds() -> int:\n    return get_int_env(\n        \"MINERU_API_TASK_RETENTION_SECONDS\",\n        DEFAULT_TASK_RETENTION_SECONDS,\n        minimum=0,\n    )\n\n\ndef get_task_cleanup_interval_seconds() -> int:\n    return get_int_env(\n        \"MINERU_API_TASK_CLEANUP_INTERVAL_SECONDS\",\n        DEFAULT_TASK_CLEANUP_INTERVAL_SECONDS,\n        minimum=1,\n    )\n","sourceCodeStart":93,"sourceCodeEnd":129,"githubUrl":"https://github.com/opendatalab/MinerU/blob/4fe4bde114a23ee5dd637eae99b767f4669bf58c/mineru/cli/router.py#L93-L129","documentation":"ValueError('{payload_name} must be a JSON object') raised by _parse_json_object_response in router.py when the worker's response parsed as valid JSON but the top-level value is not a dict (e.g. a JSON list, string, or number). The router expects every worker payload (health stats, task status) to be a JSON object, so a syntactically fine but wrong-shaped reply is rejected. Usually indicates a version mismatch between router and worker, or a different service answering on that URL.","triggerScenarios":"The worker endpoint returns a JSON array (older/newer protocol); a generic API (not a mineru worker) answering on the configured port and returning scalar/array JSON; middleware wrapping responses in a list; a hand-rolled mock worker used in tests that returns a bare list.","commonSituations":"Mixing mineru versions where the response schema changed; SERVER_URL pointing at the wrong service; test mocks not shaped like the real protocol.","solutions":["Log/inspect response.json() to see the actual top-level type and content.","Confirm the URL points at a real mineru worker of the matching version (check its /health payload shape).","Align router and worker versions (protocol_version is also verified elsewhere; mismatches surface quickly).","Fix test mocks to return objects like {'status': 'healthy', ...}."],"exampleFix":"# before (mock worker)\n@pytest.fixture\ndef health():\n    return [200, {'status': 'healthy'}]  # top-level list -> ValueError\n\n# after\n@pytest.fixture\ndef health():\n    return {'status_code': 200, 'json': {'status': 'healthy', 'protocol_version': API_PROTOCOL_VERSION}}","handlingStrategy":"type-guard","validationCode":"import json\n\ndef is_json_object_response(response) -> bool:\n    try:\n        return isinstance(response.json(), dict)\n    except ValueError:\n        return False","typeGuard":"def is_worker_payload(payload) -> bool:\n    return isinstance(payload, dict) and 'status' in payload","tryCatchPattern":"try:\n    payload = _parse_json_object_response(response, name)\nexcept ValueError as exc:\n    if 'must be a JSON object' in str(exc):\n        logger.error('worker replied with top-level %s', type(response.json()).__name__)\n    raise","preventionTips":["Shape test mocks to the real worker schema (dict with status/protocol_version/stats).","Pin router and worker to the same mineru version so payload schemas agree.","Verify /health returns a dict before enrolling a worker URL."],"tags":["mineru","json","schema","worker-communication","version-mismatch"],"backgroundTag":null,"analyzedSha":"4fe4bde114a23ee5dd637eae99b767f4669bf58c","analyzedAt":"2026-08-14T21:29:18.456Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}