{"record":{"id":"83379566daf58b09","repo":"clockworklabs/SpacetimeDB","slug":"procedure-return-value-failed-to-serialize-to-json","errorCode":null,"errorMessage":"Procedure return value failed to serialize to JSON","messagePattern":"Procedure return value failed to serialize to JSON","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/core/src/client/messages.rs","lineNumber":853,"sourceCode":"            ws_v1::ServerMessage::ProcedureResult(ws_v1::ProcedureResult {\n                status,\n                timestamp,\n                total_host_execution_duration,\n                request_id,\n            })\n        }\n\n        // Note that procedure returns are sent only to the caller, not broadcast to all subscribers,\n        // so we don't have to bother with memoizing the serialization the way we do for reducer args.\n        match protocol {\n            Protocol::Binary => ws_v1::FormatSwitch::Bsatn(convert(self, |val| {\n                bsatn::to_vec(&val)\n                    .expect(\"Procedure return value failed to serialize to BSATN\")\n                    .into()\n            })),\n            Protocol::Text => ws_v1::FormatSwitch::Json(convert(self, |val| {\n                serde_json::to_string(&SerializeWrapper(val))\n                    .expect(\"Procedure return value failed to serialize to JSON\")\n                    .into()\n            })),\n        }\n    }\n}\n","sourceCodeStart":835,"sourceCodeEnd":859,"githubUrl":"https://github.com/clockworklabs/SpacetimeDB/blob/524b4487d949b61a07d4f39c862d1290259dfd20/crates/core/src/client/messages.rs#L835-L859","documentation":"When a procedure is called over WebSocket v1 with the text (JSON) protocol, the host serializes the return value with `serde_json::to_string(&SerializeWrapper(val)).expect(\"Procedure return value failed to serialize to JSON\")`. serde_json errors on values JSON cannot represent — most famously maps with non-string keys — plus custom Serialize impls that return errors.","triggerScenarios":"Calling a procedure whose return type contains a map with integer/tuple keys, or a custom Serialize that fails, while the client subscribes with Protocol::Text (JSON WebSocket subprotocol).","commonSituations":"Returning HashMap<u32, T> or similar keyed maps to JSON-protocol clients; hand-written Serialize impls; schema/runtime skew after module edits without republishing.","solutions":["Replace non-string map keys with strings (HashMap<String, T>) or a Vec of key/value pairs for JSON clients.","Derive SpacetimeType/Serialize instead of hand-writing impls for return values.","Republish the module and regenerate bindings after any return-type change."],"exampleFix":"// before: JSON cannot serialize integer-keyed maps\nfn counts(ctx: &ReducerContext) -> HashMap<u64, u64> { ... }\n\n// after: use string keys or a vec of entries\n#[derive(spacetimedb::SpacetimeType)]\npub struct CountEntry { pub id: String, pub count: u64 }\nfn counts(ctx: &ReducerContext) -> Vec<CountEntry> { ... }","handlingStrategy":"type-guard","validationCode":"// Reject non-JSON-representable returns before exposing a procedure to text-protocol clients\n#[test]\nfn ret_json_ok() { assert!(serde_json::to_string(&sample_return()).is_ok()); }","typeGuard":"trait JsonRepresentable: serde::Serialize {} // marker for types proven round-trippable by the test above","tryCatchPattern":null,"preventionTips":["Use string keys (or Vec of entries) instead of integer-keyed maps in return types.","Derive Serialize/SpacetimeType; avoid custom impls on returns.","Test both BSATN and JSON encodings in CI since clients pick the protocol."],"tags":["rust","spacetimedb","json","serde","serialization","procedure"],"backgroundTag":"json-serialization-failed","analyzedSha":"524b4487d949b61a07d4f39c862d1290259dfd20","analyzedAt":"2026-08-16T23:58:54.611Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}