clockworklabs/SpacetimeDB · error · Error

Invalid JSON: failed to parse string

Error message

Invalid JSON: failed to parse string

What it means

parseJsonObject runs JSON.parse and converts any throw into 'Invalid JSON: failed to parse string'. In the module runtime it parses the JWT payload of an authenticated request (JwtClaimsImpl, runtime.ts:133) and is exported for handlers parsing request bodies. Any syntactically invalid JSON (trailing commas, single quotes, raw newlines, truncation, an HTML error page) triggers it.

Source

Thrown at crates/bindings-typescript/src/server/runtime.ts:110

function responseIntoWire(response: SyncResponse): [HttpResponse, Uint8Array] {
  return [
    {
      headers: serializeHeaders(response.headers),
      version: response.version,
      code: response.status,
    },
    response.bytes(),
  ];
}

export function parseJsonObject(json: string): JsonObject {
  let value: unknown;

  try {
    value = JSON.parse(json);
  } catch {
    throw new Error('Invalid JSON: failed to parse string');
  }

  if (value === null || typeof value !== 'object' || Array.isArray(value)) {
    throw new Error('Expected a JSON object at the top level');
  }

  // The runtime check above guarantees this cast is safe
  return value as JsonObject;
}

class JwtClaimsImpl implements JwtClaims {
  readonly fullPayload: JsonObject;
  private readonly _identity: Identity;
  /**
   * Creates a new JwtClaims instance.
   * @param rawPayload The JWT payload as a raw JSON string.
   * @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).
   */

View on GitHub (pinned to 524b4487d9)

Solutions

  1. Wrap the parse in try/catch and return a 400 with a clear message instead of letting the module throw
  2. Log or curl the exact raw bytes being parsed to spot truncation, BOMs, or wrong encoding
  3. If the input is a JWT, use a real three-part token from your identity provider rather than a placeholder string

Example fix

// before
const claims = parseJsonObject(bodyText); // throws on bad input

// after
let claims;
try {
  claims = parseJsonObject(bodyText);
} catch {
  return new Response(JSON.stringify({ error: 'invalid JSON body' }), { status: 400 });
}
Defensive patterns

Strategy: try-catch

Validate before calling

function tryParseJson(text: string): { ok: true; value: unknown } | { ok: false } {
  try {
    return { ok: true, value: JSON.parse(text) };
  } catch {
    return { ok: false };
  }
}
const parsed = tryParseJson(bodyText);
if (!parsed.ok) return new Response('invalid JSON', { status: 400 });

Try / catch

catch (e) { if (e instanceof Error && e.message === 'Invalid JSON: failed to parse string') return 400; throw e; }

Prevention

When it happens

Trigger: A request JWT whose payload base64url segment does not decode to valid JSON; an HTTP handler calling parseJsonObject on a request body that is not strict JSON; a body truncated or replaced by a proxy's HTML error page.

Common situations: Clients sending form-encoded or loosely quoted JSON with Content-Type: application/json; hand-crafted test tokens; upstream gateways returning an HTML 502 page where JSON was expected.

Understand the failure class

Related errors


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