{"record":{"id":"bffee5299c6d44eb","repo":"can1357/oh-my-pi","slug":"cannot-convert-a-symbol-to-a-string","errorCode":null,"errorMessage":"Cannot convert a symbol to a string","messagePattern":"Cannot convert a symbol to a string","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"packages/omptype/src/json-schema.ts","lineNumber":280,"sourceCode":"\t\tdefault:\n\t\t\treturn undefined;\n\t}\n}\n\nfunction emitObject(\n\tprops: PropIR[],\n\tindex: IR | undefined,\n\textras: \"keep\" | \"reject\" | \"delete\",\n\tctx: EmitCtx,\n): JsonSchema {\n\tconst properties: Record<string, unknown> = {};\n\tconst required: string[] = [];\n\tconst filled = (prop: PropIR): boolean => !prop.opt && (ctx.options?.io === \"output\" || !prop.hasDefault);\n\t// ArkType emits required properties first (each group in declaration\n\t// order); downstream wire consumers rely on that stable ordering.\n\tconst ordered = [...props.filter(filled), ...props.filter(prop => !filled(prop))];\n\tfor (const prop of ordered) {\n\t\tif (typeof prop.key === \"symbol\") throw new TypeError(\"Cannot convert a symbol to a string\");\n\t\tconst key = String(prop.key);\n\t\tconst propertySchema = emit(prop.val, ctx);\n\t\tif (prop.hasDefault) {\n\t\t\tpropertySchema.default = prop.defFactory ? (prop.def as () => unknown)() : prop.def;\n\t\t}\n\t\tproperties[key] = propertySchema;\n\t\tif (filled(prop)) required.push(key);\n\t}\n\tconst schema: JsonSchema = { type: \"object\", properties };\n\tif (required.length > 0) schema.required = required;\n\tif (index !== undefined) schema.additionalProperties = emit(index, ctx);\n\telse if (extras === \"reject\") schema.additionalProperties = false;\n\treturn schema;\n}\n\nfunction isJsonValue(value: unknown, seen = new Set<object>()): boolean {\n\tif (value === null || typeof value === \"string\" || typeof value === \"boolean\") return true;\n\tif (typeof value === \"number\") return Number.isFinite(value);","sourceCodeStart":262,"sourceCodeEnd":298,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/omptype/src/json-schema.ts#L262-L298","documentation":"omptype's JSON Schema emitter builds string-keyed JSON Schema objects, and JSON Schema has no representation for symbol-keyed properties. When emitObject encounters a property whose key is a symbol it cannot name it in the `properties` map, so it throws a TypeError with the engine's native 'Cannot convert a symbol to a string' message (triggered by `String(prop.key)`).","triggerScenarios":"Calling `type.toJsonSchema()` (via `emit`) on a Type whose object type includes a symbol-keyed property, e.g. `type({ [Symbol.iterator]: ... })` or an object literal with a computed symbol key that survived into the PropIR.","commonSituations":"Adding well-known-symbol members (Symbol.asyncIterator, Symbol.toStringTag, custom registry symbols) to object schemas used as DTOs; spreading objects carrying internal symbol metadata into a type definition before schema generation.","solutions":["Remove the symbol-keyed property from the type definition or move it to a non-schema object.","Filter symbol-keyed props before emitting, e.g. rebuild the object type with only string keys.","If symbols are needed internally, keep them out of the IR (declare the wire shape and augment at runtime)."],"exampleFix":"// before\nconst T = type({ id: string, [metadataKey]: string });\nT.toJsonSchema(); // throws\n\n// after\nconst T = type({ id: string });\nT.toJsonSchema(); // works","handlingStrategy":"validation","validationCode":"function hasOnlyStringKeys(t) {\n  // build the schema in a guard context\n  try { t.toJsonSchema(); return true; } catch { return false; }\n}\n// or preemptively on the raw object literal:\nconst symbolKeys = Object.getOwnPropertySymbols(raw).length > 0;\nif (symbolKeys) throw new Error('remove symbol keys before schema generation');","typeGuard":"function isSymbolKeyed(obj) {\n  return Object.getOwnPropertySymbols(obj).length > 0;\n}","tryCatchPattern":"try {\n  const schema = T.toJsonSchema();\n} catch (e) {\n  if (e instanceof TypeError && e.message.includes('symbol')) {\n    // fall back to a symbol-free variant of the type or skip schema export\n  } else throw e;\n}","preventionTips":["Keep schema/types limited to JSON-representable (string-keyed) shapes; augment symbols at runtime outside the type.","Lint against computed symbol keys in type-definition modules.","Add a CI check that renders toJsonSchema() for every exported API type."],"tags":["json-schema","symbols","serialization"],"backgroundTag":"symbol-key-not-representable-in-json-schema","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}