{"record":{"id":"d944d5dc81fa2849","repo":"can1357/oh-my-pi","slug":"field-must-be-an-object","errorCode":null,"errorMessage":"{field} must be an object","messagePattern":"(.+?) must be an object","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"python/omp-rpc/src/omp_rpc/protocol.py","lineNumber":160,"sourceCode":"\ndef _clone_json_value(value: object, *, field: str) -> JsonValue:\n    if value is None or isinstance(value, (str, int, float, bool)):\n        return cast(JsonValue, value)\n    if isinstance(value, list):\n        return [_clone_json_value(item, field=field) for item in value]\n    if isinstance(value, dict):\n        cloned: JsonObject = {}\n        for key, item in value.items():\n            if not isinstance(key, str):\n                raise ValueError(f\"{field} must contain string keys\")\n            cloned[key] = _clone_json_value(item, field=field)\n        return cloned\n    raise ValueError(f\"{field} must be JSON-serializable\")\n\n\ndef _clone_json_object(value: object, *, field: str) -> JsonObject:\n    if not isinstance(value, dict):\n        raise ValueError(f\"{field} must be an object\")\n    return cast(JsonObject, _clone_json_value(value, field=field))\n\n\ndef _optional_json_object(value: object, *, field: str) -> JsonObject | None:\n    if value is None:\n        return None\n    return _clone_json_object(value, field=field)\n\n\ndef _optional_json_objects(\n    values: object, *, field: str\n) -> tuple[JsonObject, ...] | None:\n    if values is None:\n        return None\n    if not isinstance(values, list):\n        raise ValueError(f\"{field} must be a list\")\n    return tuple(_clone_json_object(item, field=f\"{field}[]\") for item in values)\n","sourceCodeStart":142,"sourceCodeEnd":178,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/python/omp-rpc/src/omp_rpc/protocol.py#L142-L178","documentation":"`_clone_json_object` is the gatekeeper for every field the protocol expects to be a JSON object. It raises this ValueError when the supplied value is not a `dict` (e.g. it is a list, string, or number), before any deep cloning happens. This keeps malformed RPC payloads from being accepted into typed protocol structures.","triggerScenarios":"Passing a non-dict where an object field is expected: e.g. `_optional_json_object`/`parse_agent_messages`/`parse_assistant_message_event` receiving a JSON array or a JSON-encoded string like '{\"a\":1}' that was never parsed, or a field that is None being routed through the required variant `_clone_json_objects` items.","commonSituations":"Double-encoding a nested payload as a JSON string and forgetting `json.loads`; the server/API changed a field from object to list (version mismatch between client and daemon); using `payload.get('x')` and passing a list where a dict belongs; typos so a sibling field is passed as the object.","solutions":["Check the value's type at the call site and ensure it is a dict (`isinstance(value, dict)`); `json.loads` any JSON-string payloads before passing them.","If the value can be absent, use the `_optional_json_object`-backed API or pass None explicitly instead of an empty non-dict value.","Verify client/daemon protocol versions match — a schema drift may have changed this field's shape.","Log/repr the raw payload field to confirm its actual shape before parsing."],"exampleFix":"// before\nraw = '{\"options\": {\"timeout\": 5}}'\nparse_extension_ui_request({\"method\": \"select\", \"options\": raw})\n// ValueError: options must be an object\n\n// after\nimport json\nparse_extension_ui_request({\"method\": \"select\", \"options\": json.loads(raw)})","handlingStrategy":"type-guard","validationCode":"def is_json_object(value) -> bool:\n    return isinstance(value, dict) and all(isinstance(k, str) for k in value)\n\nif not is_json_object(payload.get(field)):\n    raise TypeError(f\"{field} must be a JSON object, got {type(value).__name__}\")","typeGuard":"def is_json_object(value: object) -> bool:\n    return isinstance(value, dict)","tryCatchPattern":"try:\n    msg = parse_agent_messages(raw)\nexcept ValueError as exc:\n    if \"must be an object\" in str(exc):\n        logger.warning(\"malformed payload for %s: %r\", field, raw.get(field))\n        msg = fallback_message(raw)\n    else:\n        raise","preventionTips":["Always json.loads JSON-encoded strings before embedding them as nested objects.","Use the *_optional* parse helpers when a field may legitimately be absent.","Pin and align client/daemon protocol versions.","Validate payloads with a JSON schema before handing them to the parser."],"tags":["python","validation","rpc","type-mismatch"],"backgroundTag":"schema-validation-failed","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}