can1357/oh-my-pi · error · OmpTypeError

duplicate object key ${String(key)}

Error message

duplicate object key ${String(key)}

What it means

When an object definition key is normalized (e.g. quoted, optional marker, or alias-resolved to a different canonical key) and the normalized key collides with an already-declared property that did not come from a spread, parseObjectDefinition rejects it as a duplicate. This prevents two entries silently merging or shadowing each other in the parsed IR.

Source

Thrown at packages/omptype/src/ir.ts:1421

						key,
						opt,
						val: embed(val),
						def: val.hasDefaultOutput ? val.defaultOutput : val.defaultValue,
						defFactory: typeof val.defaultValue === "function",
						hasDefault: true,
						defValidated: val.hasDefaultOutput,
					}
				: { key, opt, val: embed(val) };
		} else {
			prop = {
				key,
				opt,
				val: isObjectDefinition(val) ? parseObjectDefinition(val, resolve) : parseDef(val, resolve),
			};
		}
		if (key !== originalKey) {
			if (!spreadKeys?.has(key) && props.some(candidate => candidate.key === key)) {
				throw new OmpTypeError(`duplicate object key ${String(key)}`);
			}
			if (normalizedKey === undefined) normalizedKey = key;
			else {
				normalizedKeys ??= [normalizedKey];
				normalizedKeys.push(key);
			}
		} else if (!spreadKeys?.has(key) && (key === normalizedKey || normalizedKeys?.includes(key))) {
			throw new OmpTypeError(`duplicate object key ${String(key)}`);
		}
		if (opt && prop.hasDefault) throw new OmpTypeError(`optional key ${String(key)} cannot specify a default`);
		if (simple && (prop.hasDefault || !isSimpleIR(prop.val))) simple = false;
		addObjectProp(props, spreadKeys, prop);
	}
	for (const key of Object.getOwnPropertySymbols(def)) {
		if (!Object.prototype.propertyIsEnumerable.call(def, key)) continue;
		const val = def[key];
		let prop: PropIR;
		if (typeof val === "string") {

View on GitHub (pinned to 9690622007)

Solutions

  1. Remove one of the duplicate property entries
  2. Use spread to intentionally combine definitions (spread keys bypass this duplicate check)
  3. Ensure each source key normalizes to a unique canonical key

Example fix

// before
type({ name: "string", "'name'": "number" }) // duplicate key "name"
// after
type({ name: "string" })
Defensive patterns

Strategy: validation

Validate before calling

function assertNoNormalizedDuplicates(def) {
  const norm = (k) => k.replace(/^'+|'+$/g, "");
  const seen = new Set();
  for (const k of Object.keys(def)) {
    const n = norm(k);
    if (seen.has(n)) throw new Error(`duplicate key after normalization: ${n}`);
    seen.add(n);
  }
}

Try / catch

try { const T = type(def); } catch (e) {
  if (String(e.message).startsWith("duplicate object key")) {
    // dedupe def entries or switch to explicit spreads, then rebuild
  } else throw e;
}

Prevention

When it happens

Trigger: Declaring two entries in one object definition whose normalized keys are equal (e.g. "name" and "'name'" both present, or a quoted key and its unquoted equivalent) while the key was not introduced via spread — checked via props.some(candidate => candidate.key === key) in the key !== originalKey branch.

Common situations: Mixing quoted and unquoted spellings of the same property; alias resolution mapping two different source keys onto the same canonical name; merging hand-written definitions that both include the same key.

Related errors


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