DEV Community

56kode
56kode

Posted on

Clean code: Why prefer unknown over any in TypeScript

TypeScript offers two main types for handling values whose type is not known in advance: any and unknown. While they may seem similar, these types have fundamental differences that make unknown a safer choice in most situations.

The fundamental difference

The any type completely disables the typing system for the concerned value, while unknown is a type that requires explicit checks before any use.

// With any
let dataAny: any = JSON.parse('{"x": 10}');
console.log(dataAny.x); // OK
console.log(dataAny.nonExistent.property); // OK at compilation, error at runtime

// With unknown
let dataUnknown: unknown = JSON.parse('{"x": 10}');
console.log(dataUnknown.x); // Compilation error
Enter fullscreen mode Exit fullscreen mode

The advantages of unknown

1. Increased type safety

With unknown, you are forced to check the type before using a value, which greatly reduces runtime errors.

function processValue(value: unknown): string {
  // Required to check the type before using value
  if (typeof value === "string") {
    return value.toUpperCase();
  }

  if (typeof value === "number") {
    return value.toString();
  }

  return "Unsupported type";
}
Enter fullscreen mode Exit fullscreen mode

2. More explicit code

Using unknown makes code more explicit by forcing developers to document expected types through checks.

// With any, the real type remains implicit
function handleDataAny(data: any) {
  return data.count * 2; // What happens if data.count is not a number?
}

// With unknown, the expected type is explicitly checked
function handleDataUnknown(data: unknown) {
  if (typeof data === "object" && data !== null && "count" in data && typeof data.count === "number") {
    return data.count * 2; // Safe
  }
  throw new Error("Invalid data format");
}
Enter fullscreen mode Exit fullscreen mode

3. Better error detection

unknown allows errors to be detected at compilation rather than runtime.

let obj: any = { name: "TypeScript" };
console.log(obj.getName()); // Error at runtime

let objUnknown: unknown = { name: "TypeScript" };
// console.log(objUnknown.getName()); // Compilation error
Enter fullscreen mode Exit fullscreen mode

Practical examples of using unknown

1. Safe JSON parsing

function parseConfig(jsonString: string): { port: number } | null {
  try {
    const config: unknown = JSON.parse(jsonString);

    if (
      config && 
      typeof config === "object" && 
      "port" in config && 
      typeof config.port === "number"
    ) {
      return { port: config.port };
    }

    return null;
  } catch {
    return null;
  }
}
Enter fullscreen mode Exit fullscreen mode

2. API response handling

async function fetchUserData() {
  const response = await fetch('https://api.example.com/user');
  const data: unknown = await response.json();

  if (
    data && 
    typeof data === "object" && 
    "name" in data && 
    typeof data.name === "string"
  ) {
    return `Hello, ${data.name}!`;
  }

  return "Invalid user data";
}
Enter fullscreen mode Exit fullscreen mode

3. Custom type guards

interface User {
  id: number;
  name: string;
}

// Custom type guard
function isUser(value: unknown): value is User {
  return (
    typeof value === "object" &&
    value !== null &&
    "id" in value &&
    "name" in value &&
    typeof value.id === "number" &&
    typeof value.name === "string"
  );
}

function processUser(input: unknown) {
  if (isUser(input)) {
    // TypeScript now knows that input is of type User
    console.log(`User: ${input.name}`);
  }
}
Enter fullscreen mode Exit fullscreen mode

When to use any

Here are situations where any might be justified:

  1. Integration with libraries without type definitions - When the typing effort would be excessive for an external library without types.

  2. Gradual migration from JavaScript to TypeScript - To gradually convert an existing codebase without blocking everything.

  3. Extreme cases where the type is truly unpredictable - Very rare, but can happen in specific contexts where even with checks, the type cannot be determined with certainty.

Conclusion

The unknown type represents a safer approach than any for handling values of unknown type in TypeScript. It offers a good balance between flexibility and type safety, allowing you to work with data of unknown structure while maintaining typing rigor.

By choosing unknown by default and reserving any for exceptional cases, you will get code that is more solid, easier to maintain, and less prone to runtime errors.

Top comments (0)