{"record":{"id":"43353b99a4ca7fba","repo":"can1357/oh-my-pi","slug":"field-must-be-json-serializable","errorCode":null,"errorMessage":"{field} must be JSON-serializable","messagePattern":"(.+?) must be JSON-serializable","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"python/omp-rpc/src/omp_rpc/protocol.py","lineNumber":155,"sourceCode":")\n_AUTO_COMPACTION_ACTION_VALUES: Final[frozenset[str]] = frozenset(\n    {\"context-full\", \"handoff\", \"shake\", \"snapcompact\"}\n)\n\n\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:","sourceCodeStart":137,"sourceCodeEnd":173,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/python/omp-rpc/src/omp_rpc/protocol.py#L137-L173","documentation":"omp-rpc validates incoming RPC payloads by deep-cloning values into strict JSON types before constructing protocol dataclasses. `_clone_json_value` accepts only None, bool, int, float, str, list, and dict; anything else (e.g. datetime, bytes, Decimal, set, custom objects, or numpy scalars) raises this ValueError naming the offending field. It exists because the wire contract is pure JSON, and silently stringifying non-JSON values would hide client-side serialization bugs.","triggerScenarios":"Calling parse functions like parse_tool_descriptor, parse_compaction_result, parse_notification, or the internal _clone_json_object with a dict that contains a non-JSON-serializable value anywhere in its nesting (e.g. a `datetime` in a tool `metadata` field, `bytes` in a payload, a `set`, or a Decimal for a float field).","commonSituations":"Passing Python SDK objects (datetime, UUID, Decimal) directly into RPC payloads instead of pre-converting to ISO strings; building arguments with objects from another library (numpy types, dataclass instances) without `json.dumps`-style conversion; constructing payloads by hand rather than via `json.loads` of a JSON document.","solutions":["Locate the field named in the message and convert non-JSON values to JSON equivalents before calling the parser (datetime -> .isoformat(), UUID -> str(), Decimal -> float, set -> sorted list).","Run the payload through `json.loads(json.dumps(payload, default=str))` as a normalizer when you cannot control every nested value.","Use only values produced by `json.loads` / a JSON parser to build RPC payloads instead of hand-constructing dicts from Python objects.","Enable a strict encoder (e.g. json.dumps with no `default`) in tests to fail fast on non-serializable values."],"exampleFix":"// before\nfrom datetime import datetime\nparse_tool_descriptor({\"name\": \"run\", \"metadata\": {\"created\": datetime.now()}})\n// ValueError: metadata must be JSON-serializable\n\n// after\nparse_tool_descriptor({\"name\": \"run\", \"metadata\": {\"created\": datetime.now().isoformat()}})","handlingStrategy":"validation","validationCode":"import json\ndef is_json_serializable(value) -> bool:\n    try:\n        json.dumps(value)\n        return True\n    except (TypeError, ValueError):\n        return False\n\nassert is_json_serializable(payload), f\"{field} payload is not JSON-serializable\"","typeGuard":"JSON_PRIMITIVES = (str, int, float, bool, type(None))\ndef is_json_value(value) -> bool:\n    if isinstance(value, JSON_PRIMITIVES):\n        return True\n    if isinstance(value, list):\n        return all(is_json_value(item) for item in value)\n    if isinstance(value, dict):\n        return all(isinstance(k, str) and is_json_value(v) for k, v in value.items())\n    return False","tryCatchPattern":null,"preventionTips":["Build RPC payloads exclusively from json.loads output or JSON literals, never from live Python objects.","Convert datetime/UUID/Decimal/set values to strings or lists at payload-construction time.","Add a json.dumps round-trip assertion in tests for any payload you construct.","Remember json.dumps' default=str only masks the problem — fix the value itself."],"tags":["python","validation","rpc","json"],"backgroundTag":"non-serializable-value","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}