clockworklabs/SpacetimeDB · error

Procedure return value failed to serialize to JSON

Error message

Procedure return value failed to serialize to JSON

What it means

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.

Source

Thrown at crates/core/src/client/messages.rs:853

            ws_v1::ServerMessage::ProcedureResult(ws_v1::ProcedureResult {
                status,
                timestamp,
                total_host_execution_duration,
                request_id,
            })
        }

        // Note that procedure returns are sent only to the caller, not broadcast to all subscribers,
        // so we don't have to bother with memoizing the serialization the way we do for reducer args.
        match protocol {
            Protocol::Binary => ws_v1::FormatSwitch::Bsatn(convert(self, |val| {
                bsatn::to_vec(&val)
                    .expect("Procedure return value failed to serialize to BSATN")
                    .into()
            })),
            Protocol::Text => ws_v1::FormatSwitch::Json(convert(self, |val| {
                serde_json::to_string(&SerializeWrapper(val))
                    .expect("Procedure return value failed to serialize to JSON")
                    .into()
            })),
        }
    }
}

View on GitHub (pinned to 524b4487d9)

Solutions

  1. Replace non-string map keys with strings (HashMap<String, T>) or a Vec of key/value pairs for JSON clients.
  2. Derive SpacetimeType/Serialize instead of hand-writing impls for return values.
  3. Republish the module and regenerate bindings after any return-type change.

Example fix

// before: JSON cannot serialize integer-keyed maps
fn counts(ctx: &ReducerContext) -> HashMap<u64, u64> { ... }

// after: use string keys or a vec of entries
#[derive(spacetimedb::SpacetimeType)]
pub struct CountEntry { pub id: String, pub count: u64 }
fn counts(ctx: &ReducerContext) -> Vec<CountEntry> { ... }
Defensive patterns

Strategy: type-guard

Validate before calling

// Reject non-JSON-representable returns before exposing a procedure to text-protocol clients
#[test]
fn ret_json_ok() { assert!(serde_json::to_string(&sample_return()).is_ok()); }

Type guard

trait JsonRepresentable: serde::Serialize {} // marker for types proven round-trippable by the test above

Prevention

When it happens

Trigger: 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).

Common situations: Returning HashMap<u32, T> or similar keyed maps to JSON-protocol clients; hand-written Serialize impls; schema/runtime skew after module edits without republishing.

Related errors


AI-assisted analysis of clockworklabs/SpacetimeDB@524b4487d9 (2026-08-16). Data as JSON: /api/errors/83379566daf58b09. Report an issue: GitHub.