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
- Remove one of the duplicate property entries
- Use spread to intentionally combine definitions (spread keys bypass this duplicate check)
- 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
- Avoid mixing quoted and unquoted spellings of the same property
- When generating definitions programmatically, key an intermediate Map so duplicates collapse or fail early
- Prefer explicit spreads over concatenating definition objects
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
- invalid index signature pattern ${keyDefinition}
- optional key ${String(key)} cannot specify a default
- A default may only be specified for an object property or tu
- optional "?" marker is only valid on object property values
- thunk must return a Type (was ${typeof resolved})
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/1bef32b746031a62.
Report an issue: GitHub.