The Language APIs Speak
APIs exchange data as JSON — text like {"id": 1, "name": "Ana"}. TypeScript needs no library for it: JSON.stringify turns a value into JSON text and JSON.parse turns JSON text back into a value. Both come from JavaScript itself.
An interface describes the shape you expect on either side. There is no special syntax for renaming a field: the key in the JSON is the property name exactly as you wrote it. When a key must have a different name, you rename it by hand — when you build the object, or right after you parse it.
From Object to JSON
JSON.stringify(value) produces compact JSON for machines; JSON.stringify(value, null, 2) indents it by two spaces for humans and logs.
An optional property (written email?: string) that was never set is simply left out of the JSON — no empty key appears.
Compact and pretty JSON
Ana has no email, so the email key is missing from her JSON entirely. Ben's full record prints indented.
Output: TypeScript file and captured output
interface UserRecord {
id: number
name: string
// Optional field: when it is missing, JSON.stringify leaves the key out.
email?: string
}
const ana: UserRecord = { id: 1, name: "Ana" }
console.log(JSON.stringify(ana))
const ben: UserRecord = { id: 2, name: "Ben", email: "[email protected]" }
console.log(JSON.stringify(ben, null, 2))
From JSON to Object
JSON.parse returns the parsed value, and as UserRecord tells the type checker what you expect to find. Bad JSON does not hand back an error value — it throws, so wrap the call in try/catch.
One thing to know: unknown keys are not dropped. JSON.parse keeps every key the text contains; the interface only describes what you promise to use. When you want a clean object, copy out the fields you need.
Parsing, and the extra key
The JSON carries an extra key that the interface never mentions. Nothing fails — the key is really there at run time, and copying the two fields you want gives the clean { id: 7, name: 'Cleo' }. Broken JSON throws, and the catch prints the message.
Output: TypeScript file and captured output
interface UserRecord {
id: number
name: string
}
const text = '{"id": 7, "name": "Cleo", "extra": "ignored"}'
// JSON.parse keeps every key it finds — "as UserRecord" is only a promise
// to the type checker, never a filter at run time.
const parsed = JSON.parse(text) as UserRecord
console.log("parsed:", parsed)
// Copy out the fields you actually expect, and the extra one is gone.
const user: UserRecord = { id: parsed.id, name: parsed.name }
console.log("user: ", user)
// Broken JSON throws instead of returning an error value.
try {
JSON.parse('{"id": ')
} catch (err) {
console.log("error: ", (err as Error).message)
}
Nested Data
Real API payloads nest: an order carries its user and a list of items. Objects inside objects and arrays map to JSON objects and arrays automatically — no special handling needed.
An order with a user inside
The nested user object becomes a nested JSON object, the items array becomes a JSON array — the shape of the data is the shape of the JSON.
Output: TypeScript file and captured output
interface UserRecord {
id: number
name: string
}
interface OrderRecord {
id: number
items: string[]
user: UserRecord
}
const order: OrderRecord = {
id: 1042,
items: ["keyboard", "mouse"],
user: { id: 1, name: "Ana" },
}
console.log(JSON.stringify(order, null, 2))