skills/djankies/claude-configs/validating-type-assertions

validating-type-assertions

SKILL.md
  • Code contains as keyword or angle bracket syntax <Type>
  • Working with type assertions or type casting
  • Converting between types
  • User mentions type assertions, casting, or "as" keyword
  • Code uses assertions on external data
  • Type Guard: Runtime check that narrows types safely
  • Type Assertion: Compile-time instruction telling TypeScript to trust you

Type assertions are TypeScript compiler directives, not runtime operations.

const data = JSON.parse(json) as User;

This compiles fine but provides ZERO runtime safety. If JSON is malformed, your code crashes.

Rule: Type assertions are safe only when YOU control the data or have already validated it.

Question 1: Where does this data come from?

  • From your own code (constants, constructors) → Assertion OK
  • From external source (API, user input, JSON) → NEVER assert, validate instead

Question 2: Have you validated the data?

  • Yes, with runtime validation → Assertion OK after validation
  • No validation → NEVER assert

Question 3: Is this a TypeScript limitation?

  • Yes (const assertions, narrowing limitations) → Assertion OK
  • No (trying to skip validation) → NEVER assert

❌ Asserting external API data

async function fetchUser(id: string): Promise<User> {
  const response = await fetch(`/api/users/${id}`);
  const data = await response.json() as User;
  return data;
}

Problem: If API returns different structure, runtime crash. TypeScript provides no protection.

✅ Validate instead

async function fetchUser(id: string): Promise<User> {
  const response = await fetch(`/api/users/${id}`);
  const data: unknown = await response.json();

  if (!isUser(data)) {
    throw new Error("Invalid user data from API");
  }

  return data;
}

❌ Asserting JSON.parse result

const config = JSON.parse(configString) as Config;

Problem: If JSON is malformed or wrong shape, crash at runtime.

✅ Validate with Zod

const data: unknown = JSON.parse(configString);
const config = ConfigSchema.parse(data);

❌ Asserting user input

function handleSubmit(formData: FormData) {
  const user = {
    name: formData.get("name"),
    email: formData.get("email")
  } as User;

  saveUser(user);
}

Problem: FormData can contain anything. No validation.

✅ Validate form data

function handleSubmit(formData: FormData) {
  const data = {
    name: formData.get("name"),
    email: formData.get("email")
  };

  const user = UserSchema.parse(data);
  saveUser(user);
}

Example 2: Safe Assertions (OK to use)

✅ Const assertions

const routes = [
  { path: "/", component: "Home" },
  { path: "/about", component: "About" }
] as const;

type Route = typeof routes[number];

Safe because: Data is hardcoded, not external.


✅ After validation

async function fetchUser(id: string): Promise<User> {
  const response = await fetch(`/api/users/${id}`);
  const data: unknown = await response.json();

  const result = UserSchema.safeParse(data);
  if (!result.success) {
    throw new Error("Invalid user data");
  }

  return result.data as User;
}

Safe because: Data validated before assertion. (Though result.data already has correct type, so assertion is redundant.)


✅ Constructor results

class User {
  constructor(
    public id: string,
    public name: string
  ) {}
}

const users = [
  new User("1", "Alice"),
  new User("2", "Bob")
] as User[];

Safe because: You control construction, types are guaranteed.


✅ Type narrowing limitations

interface Circle { kind: "circle"; radius: number; }
interface Square { kind: "square"; size: number; }
type Shape = Circle | Square;

function getArea(shape: Shape): number {
  if (shape.kind === "circle") {
    return Math.PI * shape.radius ** 2;
  }
  return shape.size ** 2;
}

Safe because: TypeScript narrows to Square after checking for circle. Use else to avoid assertion.


✅ Type widening prevention

const config = {
  apiUrl: "https://api.example.com",
  timeout: 5000
} as const;

Safe because: Preventing literal types from widening to general types.


✅ Unknown to specific after validation

function processError(error: unknown): string {
  if (error instanceof Error) return error.message;
  if (typeof error === "string") return error;
  return String(error);
}

Safe because: Type guards narrow before use without assertions.


Example 3: Double Assertion Anti-Pattern

❌ Double assertion to bypass safety

const value = "not a number" as unknown as number;

Problem: Intentionally bypassing type system. Defeats TypeScript's purpose.

✅ Fix the types properly

const value: unknown = "not a number";

if (typeof value === "number") {
  console.log(value.toFixed(2));
}

Example 4: Non-null Assertion

❌ Non-null assertion on external data

const user = await fetchUser(id);
console.log(user!.name);

Problem: If fetchUser can return null, this crashes.

✅ Check explicitly

const user = await fetchUser(id);

if (user) {
  console.log(user.name);
} else {
  console.log("User not found");
}

✅ Non-null assertion after explicit check

const element = document.getElementById("root");

if (!element) {
  throw new Error("Root element not found");
}

element.appendChild(child);

Safe because: Checked for null and threw. TypeScript narrows automatically, no assertion needed.


Example 5: Assertion Functions vs Assertions

❌ Assertion to avoid validation

function getUser(data: unknown): User {
  return data as User;
}

✅ Assertion function with validation

function assertIsUser(data: unknown): asserts data is User {
  if (!isUser(data)) {
    throw new Error("Invalid user data");
  }
}

function getUser(data: unknown): User {
  assertIsUser(data);
  return data;
}

For related patterns:

  • Runtime Validation: Use the using-runtime-checks skill for proper validation with Zod
  • Type Guards: Use the using-type-guards skill for safe type narrowing
  • Unknown Type: Use the avoiding-any-types skill for handling unknown data safely
  • Validate external data with type guards or validation libraries
  • Use assertion functions (asserts value is Type) over direct assertions
  • Check for null/undefined before non-null assertions (!)
  • Document WHY assertion is safe when used

SHOULD:

  • Prefer type guards over assertions
  • Use as const for literal type inference
  • Limit assertions to known-safe scenarios
  • Consider if assertion indicates missing validation

NEVER:

  • Assert on external data without validation (APIs, JSON, user input)
  • Use double assertions (as unknown as Type)
  • Use non-null assertion (!) without prior check
  • Assert to silence compiler errors (fix the types instead)
  • Trust that data "will be correct"

Type assertion is safe when ALL of these are true:

  • Data source is internal/controlled (not external)
  • OR data has been validated with runtime checks
  • You understand what the assertion does (compiler directive, not runtime check)
  • Assertion is documented with reason
  • No double assertions being used

If ANY checkbox is false, use validation instead.

Pattern 1: Validated Assertion

function parseUser(data: unknown): User {
  const result = UserSchema.safeParse(data);

  if (!result.success) {
    throw new ValidationError("Invalid user", result.error);
  }

  return result.data;
}

Pattern 2: Const Assertion for Config

const API_ENDPOINTS = {
  users: "/api/users",
  posts: "/api/posts",
  comments: "/api/comments"
} as const;

type Endpoint = typeof API_ENDPOINTS[keyof typeof API_ENDPOINTS];

Pattern 3: Assertion Function

function assertNever(value: never): never {
  throw new Error(`Unexpected value: ${value}`);
}

function handleShape(shape: Shape) {
  switch (shape.kind) {
    case "circle":
      return Math.PI * shape.radius ** 2;
    case "square":
      return shape.size ** 2;
    default:
      assertNever(shape);
  }
}

Pattern 4: Type Predicate Instead of Assertion

function isDefined<T>(value: T | null | undefined): value is T {
  return value !== null && value !== undefined;
}

const values = [1, null, 2, undefined, 3];
const defined = values.filter(isDefined);

Find assertions: grep -rn " as " src/ and grep -rn "!" src/ | grep -v "!=="

Classify each: External data → add validation; After validation → verify; Const assertion → keep; Bypassing types → fix types

Replace pattern:

const data = JSON.parse(json) as User;

Becomes:

const data: unknown = JSON.parse(json);
const user = UserSchema.parse(data);

Enable strict mode: Set "strict": true and "noImplicitAny": true in tsconfig.json

Weekly Installs
2
First Seen
Feb 4, 2026
Installed on
opencode2
gemini-cli2
replit2
junie2
windsurf2
antigravity2