JavaScript & TypeScript DISCUSSION

TypeScript unknown vs any: which should I use for JSON.parse results and API responses?

Started by zull TypeScript unknownany typetype guardstype narrowingruntime validation
5 replies 248 views 6 participants
Latest activity · 30 Sep 2026

TypeScript unknown vs any: which should I use for JSON.parse results and API responses?

#1

My TypeScript code parses messages from a device: const msg = JSON.parse(text); and then reads msg.payload.temp.toFixed(1). It compiles without complaint and then crashes at run time with "Cannot read properties of undefined" when the device sends an error message with a different shape. The linter suggests replacing any with unknown, but then every property access becomes a compile error.

What is the practical difference between the two types, and how am I supposed to get from unknown to a usable typed object without casting everything?

Community replies 5

Re: TypeScript unknown vs any: which should I use for JSON.parse results and API responses?

#2

Both accept any value. The difference is what you may do afterwards. any switches the type checker off: every property access, call or assignment to another type compiles, and the result of each is any again, so it spreads through the code. msg.payload.temp is any, and whatever you compute from it is unchecked too.

unknown is the type-safe counterpart. You cannot access properties on it, call it or assign it to anything other than unknown or any until you have narrowed it to something specific. JSON.parse is declared to return any, which is why your code compiled. Annotating the result, const msg: unknown = JSON.parse(text);, makes the compiler demand the checks.

Re: TypeScript unknown vs any: which should I use for JSON.parse results and API responses?

#3

Narrowing uses ordinary run-time checks that the compiler understands: typeof x === 'string', Array.isArray(x), x instanceof Date, and for objects typeof x === 'object' && x !== null && 'temp' in x. The null check is needed because typeof null is 'object'.

Package the checks as a type guard, a function whose return type is a type predicate: function isReading(x: unknown): x is Reading { return typeof x === 'object' && x !== null && 'temp' in x && typeof x.temp === 'number'; }. This form needs TypeScript 4.9 or later, which narrows on the in check. After if (isReading(msg)) the compiler treats msg as a Reading inside the block, and the check really did run.

Re: TypeScript unknown vs any: which should I use for JSON.parse results and API responses?

#4

Contrast that with a cast. const msg = JSON.parse(text) as Reading; compiles and checks nothing. A type assertion is erased at compile time, so if the device sends something else you crash in the same place as before, only now the types are lying to you. All TypeScript types are erased; there is no run-time type information to validate against, so validation has to be code.

With more than a couple of message shapes, hand-written guards get tedious. Schema libraries such as Zod let you declare the shape once and derive both the validator and the static type: const Reading = z.object({ temp: z.number() });, type Reading = z.infer<typeof Reading>;, and Reading.safeParse(data) returns either typed data or a list of problems.

Re: TypeScript unknown vs any: which should I use for JSON.parse results and API responses?

#5

For messages with several shapes, model them as a discriminated union: type Msg = { kind: 'reading'; temp: number } | { kind: 'error'; code: number };. After validation, switch (msg.kind) narrows each branch. Add a default branch that assigns msg to a variable of type never, and the compiler tells you when someone adds a third kind and forgets to handle it.

For optional fields declare temp?: number; the compiler then forces a check, or an expression such as msg.temp?.toFixed(1) ?? 'n/a'. Turn on strict in tsconfig. It includes strictNullChecks, without which the whole "undefined" class of crash is invisible to the compiler.

Re: TypeScript unknown vs any: which should I use for JSON.parse results and API responses?

#6

As a rule of thumb, use unknown at every boundary where data enters from outside: JSON.parse, response bodies, localStorage, message events. Caught exceptions belong on that list too; under strict the variable in catch (e) is unknown (since TypeScript 4.4), so write if (e instanceof Error) console.log(e.message);.

any is acceptable as a temporary escape hatch while migrating JavaScript, or around a badly typed third-party API, ideally confined to one line with a comment. The typescript-eslint rule no-explicit-any and the no-unsafe-* family show where any leaks, including the implicit one from JSON.parse. Generics are the third tool: function first<T>(xs: T[]): T | undefined keeps the caller's type where any would lose it.

TEP COMMUNITY