opendatalab/MinerU · error · ValueError
{payload_name} must be a JSON object
Error message
{payload_name} must be a JSON object What it means
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.
Source
Thrown at mineru/cli/router.py:111
try:
value = int(os.getenv(name, str(default)))
except ValueError:
return default
if value < minimum:
return default
return value
def _parse_json_object_response(
response: httpx.Response,
payload_name: str,
) -> dict[str, Any]:
try:
payload = response.json()
except ValueError as exc:
raise ValueError(f"{payload_name} is not valid JSON") from exc
if not isinstance(payload, dict):
raise ValueError(f"{payload_name} must be a JSON object")
return payload
def get_task_retention_seconds() -> int:
return get_int_env(
"MINERU_API_TASK_RETENTION_SECONDS",
DEFAULT_TASK_RETENTION_SECONDS,
minimum=0,
)
def get_task_cleanup_interval_seconds() -> int:
return get_int_env(
"MINERU_API_TASK_CLEANUP_INTERVAL_SECONDS",
DEFAULT_TASK_CLEANUP_INTERVAL_SECONDS,
minimum=1,
)
View on GitHub (pinned to 4fe4bde114)
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', ...}.
Example fix
# before (mock worker)
@pytest.fixture
def health():
return [200, {'status': 'healthy'}] # top-level list -> ValueError
# after
@pytest.fixture
def health():
return {'status_code': 200, 'json': {'status': 'healthy', 'protocol_version': API_PROTOCOL_VERSION}} Defensive patterns
Strategy: type-guard
Validate before calling
import json
def is_json_object_response(response) -> bool:
try:
return isinstance(response.json(), dict)
except ValueError:
return False Type guard
def is_worker_payload(payload) -> bool:
return isinstance(payload, dict) and 'status' in payload Try / catch
try:
payload = _parse_json_object_response(response, name)
except ValueError as exc:
if 'must be a JSON object' in str(exc):
logger.error('worker replied with top-level %s', type(response.json()).__name__)
raise Prevention
- 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.
When it happens
Trigger: 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.
Common situations: Mixing mineru versions where the response schema changed; SERVER_URL pointing at the wrong service; test mocks not shaped like the real protocol.
Related errors
- {payload_name} is not valid JSON
- Unsupported protocol_version={protocol_version}, expected {A
- max_concurrent_requests must be a positive integer
- Timed out waiting for local worker {server_id} to become hea
- MinerU upstream returned an invalid submit payload
AI-assisted analysis of opendatalab/MinerU@4fe4bde114 (2026-08-14).
Data as JSON: /api/errors/ddc6c8c78f3e3f14.
Report an issue: GitHub.