{"record":{"id":"b71ac94b3408d0ad","repo":"clockworklabs/SpacetimeDB","slug":"invalid-json-failed-to-parse-string","errorCode":null,"errorMessage":"Invalid JSON: failed to parse string","messagePattern":"Invalid JSON: failed to parse string","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"crates/bindings-typescript/src/server/runtime.ts","lineNumber":111,"sourceCode":"\nfunction responseIntoWire(response: SyncResponse): [HttpResponse, Uint8Array] {\n  return [\n    {\n      headers: serializeHeaders(response.headers),\n      version: response.version,\n      code: response.status,\n    },\n    response.bytes(),\n  ];\n}\n\nexport function parseJsonObject(json: string): JsonObject {\n  let value: unknown;\n\n  try {\n    value = JSON.parse(json);\n  } catch {\n    throw new Error('Invalid JSON: failed to parse string');\n  }\n\n  if (value === null || typeof value !== 'object' || Array.isArray(value)) {\n    throw new Error('Expected a JSON object at the top level');\n  }\n\n  // The runtime check above guarantees this cast is safe\n  return value as JsonObject;\n}\n\nclass JwtClaimsImpl implements JwtClaims {\n  readonly fullPayload: JsonObject;\n  private readonly _identity: Identity;\n  /**\n   * Creates a new JwtClaims instance.\n   * @param rawPayload The JWT payload as a raw JSON string.\n   * @param identity The identity for this JWT. We are only taking this because we don't have a blake3 implementation (which we need to compute it).\n   */","sourceCodeStart":93,"sourceCodeEnd":129,"githubUrl":"https://github.com/clockworklabs/SpacetimeDB/blob/eddf9f5014579a50d4b67630e28b6e15cad9c4af/crates/bindings-typescript/src/server/runtime.ts#L93-L129","documentation":"`parseJsonObject` wraps `JSON.parse` and rethrows a bare string parse failure as `Invalid JSON: failed to parse string` (a sibling check enforces a top-level object). Its main internal caller is `JwtClaimsImpl`, which parses the raw JWT payload of the caller's token when you access `ctx.auth.jwt` inside a reducer/procedure. So this error usually means the connection presented a token whose payload segment is not valid JSON, or you passed a non-JSON string to `parseJsonObject`/claim parsing yourself.","triggerScenarios":"Accessing `ctx.auth.jwt` (claims) when the sender connected with a custom/malformed token whose payload is not valid JSON; calling `parseJsonObject` on a truncated string, empty string, or on a JSON array/scalar is fine for this specific message only if the string itself fails to parse (arrays instead hit the sibling 'Expected a JSON object' error); passing a base64-encoded (undecoded) payload string.","commonSituations":"Generating a custom auth token with a signing library but forgetting to base64url-encode/decode the JSON payload; hand-crafting tokens in tests; a token-issuing service that emits `undefined` payload or URL-safe base64 with padding stripped incorrectly; a gateway rewriting headers.","solutions":["Reproduce the token payload outside the module: base64url-decode the middle segment of the token and run `JSON.parse` on it to see the exact parse problem.","Fix the token issuer so the JWT payload segment is base64url-encoded UTF-8 JSON (e.g. `base64url(JSON.stringify(claims))`).","If you call `parseJsonObject` yourself, validate/trim the input first and wrap the call in try/catch to treat malformed input as an application error.","In tests, build tokens with a standard JWT library instead of concatenating strings by hand."],"exampleFix":"// before\nconst claims = ctx.auth.jwt; // throws: Invalid JSON: failed to parse string\n\n// after — guard before touching claims, treat bad token as unauthenticated\nlet claims: JwtClaims | null = null;\ntry {\n  claims = ctx.auth.jwt;\n} catch {\n  ctx.log.error('sender presented a malformed token');\n}","handlingStrategy":"try-catch","validationCode":"// Validate a token's payload is JSON before relying on ctx.auth.jwt.\nfunction tokenPayloadParses(token: string): boolean {\n  const parts = token.split('.');\n  if (parts.length !== 3) return false;\n  try {\n    const json = atob(parts[1].replace(/-/g, '+').replace(/_/g, '/'));\n    return JSON.parse(json) !== null && typeof JSON.parse(json) === 'object';\n  } catch {\n    return false;\n  }\n}","typeGuard":null,"tryCatchPattern":"try { const claims = ctx.auth.jwt; /* ... */ } catch (e) { if (e instanceof Error && e.message.includes('Invalid JSON')) { /* treat sender as unauthenticated */ } else throw e; }","preventionTips":["Issue tokens with a standard JWT library; never hand-concatenate segments.","Base64url-encode and decode the payload segment exactly (strip/restore padding).","In reducers that read claims, always guard the access and log the sender identity when the payload fails to parse."],"tags":["json","jwt","auth","runtime","spacetimedb"],"backgroundTag":"invalid-json","analyzedSha":"eddf9f5014579a50d4b67630e28b6e15cad9c4af","analyzedAt":"2026-08-20T06:08:37.179Z","contentChangedAt":"2026-08-20T06:08:37.179Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}