Skip to Content
TypeScript Intermediate: Types That Survive Real Projects
Intermediate
0 likes
Courses
TypeScript Intermediate: Types That Survive Real Projects

unknown vs any and Safe Parsing

Use unknown for data from outside, narrow it with typeof, in and instanceof, and parse text into a typed result. After this, an any from one JSON.parse no longer spreads through your code.

Course content

Sign in to save your progress.

Course Home

Skip ahead: If you already know that unknown must be checked before use and that a catch variable is unknown in strict mode, skip to "Parse text into a type you can trust", then continue.

any and unknown both accept every value. That is the only thing they have in common. any says "do not check this". unknown says "I do not know what this is yet, so check it before you use it". Reaching for unknown when data comes from outside is a small habit that changes how much of your code the compiler can protect. The Beginner course warned you about any. This lesson shows the replacement.

Where any spreads

const settings = JSON.parse('{"retries": "three"}');
const retries: number = settings.retries;

console.log(retries.toFixed(0));

This compiles with no errors, and retries is declared a number. JSON.parse returns any, so every property you read from it is any, and any fits into every type. The wrong value is now inside your program with a number label, and toFixed will fail on the string. The any did not stay where it was written. It followed the value.

unknown: accept anything, trust nothing

const value: unknown = JSON.parse('{"retries": 3}');

console.log(value.retries);
console.log(value.toFixed());
const copy: number = value;
unknown_err.ts(3,13): error TS18046: 'value' is of type 'unknown'.
unknown_err.ts(4,13): error TS18046: 'value' is of type 'unknown'.
unknown_err.ts(5,7): error TS2322: Type 'unknown' is not assignable to type 'number'.

You can assign anything to an unknown. You can do almost nothing with it until you have checked. That is the whole difference, and it is the useful one.

Narrow it with a check

The same narrowing you learned for unions works on unknown: typeof, Array.isArray, in and instanceof.

function describe(value: unknown): string {
  if (typeof value === "string") {
    return `text of length ${value.length}`;
  }
  if (typeof value === "number") {
    return `number ${value.toFixed(1)}`;
  }
  if (Array.isArray(value)) {
    return `list of ${value.length}`;
  }
  if (typeof value === "object" && value !== null && "name" in value) {
    return `object with a name`;
  }
  return "something else";
}

console.log(describe("abc"));
console.log(describe(4));
console.log(describe([1, 2]));
console.log(describe({ name: "Ana" }));
console.log(describe(null));
text of length 3
number 4.0
list of 2
object with a name
something else

Inside each if, the compiler knows the type, so value.length and value.toFixed(1) are allowed. Note the value !== null before the in check. typeof null is "object", and in on null throws.

The catch variable is unknown too

try {
  JSON.parse("{ not json");
} catch (error) {
  console.log(error.message);
}
catch_err.ts(4,15): error TS18046: 'error' is of type 'unknown'.

With strict on, the variable in a catch is unknown. That is correct, because JavaScript lets you throw anything, including a string. Check what you caught.

try {
  JSON.parse("{ not json");
} catch (error) {
  if (error instanceof Error) {
    console.log(error.name);
  } else {
    console.log("something that is not an Error was thrown");
  }
}
SyntaxError

Parse text into a type you can trust

Now put it together. This function takes text and gives back either a real Settings or a reason it could not. The Result type is the one from the generics section.

type Settings = { retries: number; label: string };
type Result<T> = { ok: true; value: T } | { ok: false; error: string };

function parseSettings(text: string): Result<Settings> {
  let data: unknown;
  try {
    data = JSON.parse(text);
  } catch {
    return { ok: false, error: "not valid JSON" };
  }
  if (typeof data !== "object" || data === null) {
    return { ok: false, error: "expected an object" };
  }
  const record = data as Record<string, unknown>;
  if (typeof record.retries !== "number") {
    return { ok: false, error: "retries must be a number" };
  }
  if (typeof record.label !== "string") {
    return { ok: false, error: "label must be a string" };
  }
  return { ok: true, value: { retries: record.retries, label: record.label } };
}

const inputs = [
  '{"retries": 3, "label": "nightly"}',
  "{ not json",
  '{"retries": "three", "label": "nightly"}',
  "[]",
];

for (const text of inputs) {
  const result = parseSettings(text);
  console.log(result.ok ? `ok, ${result.value.retries} retries` : result.error);
}
ok, 3 retries
not valid JSON
retries must be a number
retries must be a number

Look at what the function returns on success. It builds a fresh object from checked fields, not the parsed object itself. Extra fields from the input are gone, and every field that remains was checked. The array [] is the case people forget: typeof [] === "object", so it passes the first check and fails on retries. Callers must look at ok before they get to value, so they cannot skip the error path.

When to use which

  • unknown for anything that arrives from outside: parsed JSON, form data, a message from another window, a catch.
  • A real type for anything you control.
  • any only for a short, temporary hack. If you do write it, leave a comment that says why.

⚠ Warning: as on an unknown is the same trap as before. value as Settings compiles, and nothing was checked. The check is the work. The cast is only the receipt.

Common mistakes

  • Writing catch (error: any) to make the error go away. Check with instanceof Error instead.
  • Returning the parsed object after checking only some of its fields.
  • Forgetting null and arrays when checking for an object.
  • Using unknown and then casting on the next line.

💡 Tip: Return a Result, not a thrown error, from a function whose job is to parse. Callers then handle the failure case in the open.

If you skip this, any from one JSON.parse leaks into five functions, and the compiler stops checking all of them without telling you. Which function in your code returns data it has not looked at?

About

Use unknown for data from outside, narrow it with typeof, in and instanceof, and parse text into a typed result. After this, an any from one JSON.parse no longer spreads through your code.

Duration
5 min
Last update
10/16/2026
Was this helpful?
​ 0 ​ 0

Comments (0)

Sign in to comment

No comments yet.