TypeScript / Editorial solution guide
Replace assertion-based trust with runtime validation
Treat external values as unknown, inspect the accepted shape and domain, and return selected validated data instead of claiming a cast repaired the input.
Read-only knowledge base ยท No public comments
Recognize the symptoms
Code compiles after an assertion but later fails on a method call, produces NaN, or serializes unexpected fields. A common pattern parses JSON and immediately asserts an interface or generic T. The successful check reflects the assertion supplied by the author, not evidence that the input bytes satisfy that type.
The issue can remain hidden when tests use only valid payloads. Replacing unknown with any or adding a non-null assertion makes the checker quieter while leaving malformed objects and missing fields unchanged at runtime.
Understand the likely causes
- Type assertions are erased and perform no general runtime conversion or schema validation. Interfaces and generic parameters are also erased. JSON syntax validity is separate from object shape, and a numeric field type is separate from finite integer or range requirements. A claimed user-defined predicate can be wrong as well if its implementation does not check the required properties.
- A value can pass a shallow shape check and still be unauthorized, too large, or mutable through another alias. Validation, normalization, permission, and stable ownership are distinct responsibilities. Choose explicit error handling for malformed syntax and rejected domain values rather than relying on whatever exception a later method happens to throw.
Identify what the assertion actually promises
The intentionally flawed excerpt asserts a numeric count even though the JSON contains text. It can pass the static checker because the author made that claim; the assertion does not inspect or convert the property. The snippet is marked non-runnable and has no promised successful output. Do not replace the assertion with any or another cast. Write down the real accepted shape and numeric domain before constructing a repair.
export {};
const data = JSON.parse('{"count":"3"}') as { count: number };
console.log(data.count.toFixed(0));
Intentionally misleading assertion; checker acceptance does not establish runtime validity.
Decode the actual unknown value
The correction starts at unknown, rejects null and non-objects, checks the required field, and validates a safe integer range. It returns a new selected object so unrelated fields are not accidentally propagated. The ordinary parsed-JSON assumption matters: arbitrary proxies or getters need an additional property-access policy. No assertion is required because runtime observations provide the narrowing evidence. A real caller should also catch JSON syntax errors before passing a parsed value to this decoder.
export {};
type CountData = { count: number };
function decode(value: unknown): CountData | undefined {
if (typeof value !== "object" || value === null || !("count" in value)) { return undefined; }
const count = value.count;
if (typeof count !== "number" || !Number.isSafeInteger(count) || count < 0 || count > 100) { return undefined; }
return { count };
}
const valid: unknown = JSON.parse('{"count":3}');
const invalid: unknown = JSON.parse('{"count":"3"}');
console.log(JSON.stringify(decode(valid)));
console.log(decode(invalid) === undefined);
{"count":3}
true
Numeric three is accepted. Text containing the digit three is rejected without coercion or a cast.
Maintain validity after successful decoding
Decide whether extra fields are ignored, selected away, or rejected. Define normalization separately if numeric text should be accepted; do not silently expand the domain. Bound arrays and nesting to application needs, and ensure validated mutable data cannot be changed unexpectedly through aliases before use. For a public endpoint, enforce authorization after identifying the trusted actor. A sophisticated output type does not perform any of those operations. Use structured errors if callers need field categories rather than a single undefined result.
Verify the fix
Compile and run the corrected complete example with the course flags. It prints {"count":3} and true on separate lines. Keep the misleading assertion snippet out of runtime execution; compile-only acceptance can demonstrate precisely why the original static signal was insufficient.
Test null, primitives, missing count, numeric text, fractions, infinity, negative values, zero, and both range boundaries. Verify actual output keys when private input fields exist. Add malformed-JSON handling separately, because the decoder receives a parsed value rather than raw syntax.
Keep it from recurring
- Mark untrusted boundaries as unknown, require executable validation before domain use, and review assertions as explicit trust claims. Maintain negative fixtures and distinguish validation from authorization and output filtering.