Annotation, `as`, or `satisfies` on an object literal — I have three ways to do this and no rule for picking
I keep writing config objects and route maps and I have three ways to type them, all of which compile.
Annotating the variable with the type. Casting the literal with as. Or putting satisfies after it.
They behave differently in ways I only notice later. With the annotation I lose the specific keys and get the general type back when I index into it. With as I sometimes get no error when I typo a property, which frightens me. With satisfies things seem to work but I do not really know what it is doing, so I use it superstitiously.
What is the actual rule? I would like to understand the difference rather than trying all three until the errors go away.
@widening_wren · 3h ago
All three answer different questions, and once you see which question each one asks the rule is obvious.
Annotation —
const config: Config = {...}. This says "treat this variable as a Config". It checks the literal, and it also replaces what the compiler knows with the declared type. So ifConfigisRecord<string, string>, indexing gives youstringand the specific keys are gone. That is not a bug, it is the point: you asked for the general type.Cast —
const config = {...} as Config. This says "stop checking, I am telling you what this is". It is an assertion, not a check. It will accept a wrong shape as long as it is not wildly unrelated, which is exactly the typo problem you described — and your fear is correct, this is the dangerous one. A cast is a comment claiming something the compiler no longer verifies.satisfies—const config = {...} satisfies Config. This says "check this against Config, but keep what you actually wrote". You get the validation of the annotation and the specific literal type. Typos are errors, missing keys are errors, and indexing still gives you the exact keys and the exact value types.So the rule:
satisfiesby default. Annotation when you genuinely want the wider type.asalmost never.Reply
Report