can1357/oh-my-pi · error · TypeError

Cannot convert a symbol to a string

Error message

Cannot convert a symbol to a string

What it means

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)`).

Source

Thrown at packages/omptype/src/json-schema.ts:280

		default:
			return undefined;
	}
}

function emitObject(
	props: PropIR[],
	index: IR | undefined,
	extras: "keep" | "reject" | "delete",
	ctx: EmitCtx,
): JsonSchema {
	const properties: Record<string, unknown> = {};
	const required: string[] = [];
	const filled = (prop: PropIR): boolean => !prop.opt && (ctx.options?.io === "output" || !prop.hasDefault);
	// ArkType emits required properties first (each group in declaration
	// order); downstream wire consumers rely on that stable ordering.
	const ordered = [...props.filter(filled), ...props.filter(prop => !filled(prop))];
	for (const prop of ordered) {
		if (typeof prop.key === "symbol") throw new TypeError("Cannot convert a symbol to a string");
		const key = String(prop.key);
		const propertySchema = emit(prop.val, ctx);
		if (prop.hasDefault) {
			propertySchema.default = prop.defFactory ? (prop.def as () => unknown)() : prop.def;
		}
		properties[key] = propertySchema;
		if (filled(prop)) required.push(key);
	}
	const schema: JsonSchema = { type: "object", properties };
	if (required.length > 0) schema.required = required;
	if (index !== undefined) schema.additionalProperties = emit(index, ctx);
	else if (extras === "reject") schema.additionalProperties = false;
	return schema;
}

function isJsonValue(value: unknown, seen = new Set<object>()): boolean {
	if (value === null || typeof value === "string" || typeof value === "boolean") return true;
	if (typeof value === "number") return Number.isFinite(value);

View on GitHub (pinned to 9690622007)

Solutions

  1. Remove the symbol-keyed property from the type definition or move it to a non-schema object.
  2. Filter symbol-keyed props before emitting, e.g. rebuild the object type with only string keys.
  3. If symbols are needed internally, keep them out of the IR (declare the wire shape and augment at runtime).

Example fix

// before
const T = type({ id: string, [metadataKey]: string });
T.toJsonSchema(); // throws

// after
const T = type({ id: string });
T.toJsonSchema(); // works
Defensive patterns

Strategy: validation

Validate before calling

function hasOnlyStringKeys(t) {
  // build the schema in a guard context
  try { t.toJsonSchema(); return true; } catch { return false; }
}
// or preemptively on the raw object literal:
const symbolKeys = Object.getOwnPropertySymbols(raw).length > 0;
if (symbolKeys) throw new Error('remove symbol keys before schema generation');

Type guard

function isSymbolKeyed(obj) {
  return Object.getOwnPropertySymbols(obj).length > 0;
}

Try / catch

try {
  const schema = T.toJsonSchema();
} catch (e) {
  if (e instanceof TypeError && e.message.includes('symbol')) {
    // fall back to a symbol-free variant of the type or skip schema export
  } else throw e;
}

Prevention

When it happens

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

Common situations: 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.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/bffee5299c6d44eb. Report an issue: GitHub.