Step 4 — JSON parse & stringify
We have a Bookmark type and a way to validate one. Now let’s give the service some state: persist a list of bookmarks to bookmarks.json on disk, and load it back on startup.
This is where two of typia’s most pragmatic functions earn their keep:
typia.json.assertParse—JSON.parse+ validation in one call. Refuses to start the program if the file on disk has been hand-edited into nonsense.typia.json.assertStringify—JSON.stringify, but much faster (typia emits a hand-rolled stringifier for your type), with a quick “is this actually a Bookmark?” check up front.
The store
Make a new file:
import { promises as fs } from "node:fs";
import typia from "typia";
import { Bookmark } from "./bookmark";
const FILE = "bookmarks.json";
/** Load and validate the bookmarks file. */
export async function loadAll(): Promise<Bookmark[]> {
try {
const text = await fs.readFile(FILE, "utf8");
return typia.json.assertParse<Bookmark[]>(text);
} catch (err) {
if ((err as NodeJS.ErrnoException).code === "ENOENT") return [];
throw err; // bad JSON or schema mismatch → surface it
}
}
/** Atomically write all bookmarks back to disk. */
export async function saveAll(items: Bookmark[]): Promise<void> {
const text = typia.json.assertStringify<Bookmark[]>(items);
await fs.writeFile(FILE, text, "utf8");
}Two things worth pointing out:
assertParse<Bookmark[]>doesn’t only validate the array — it also checks every constraint on every field (URL format, max length, …). Editbookmarks.jsonby hand and put"createdAt": "yesterday"in one entry, the nextloadAll()throws on that field’s path. You get to choose whether to surface the error or fall back to an empty list, but you never get a half-corrupt in-memory model.assertStringify<Bookmark[]>is the validating sibling ofJSON.stringify. It runs your data through a quickassertfirst (so you don’t accidentally serialize garbage), then runs typia’s bespoke serializer — which is several times faster than the native one because it knows exactly which fields exist onBookmark.
Use
typia.json.stringify<T>(noassert) when you’ve just constructed the value yourself and there’s no way for it to be wrong. Otherwise,assertStringifyis the safe default.
Use it
import typia from "typia";
import { Bookmark } from "./bookmark";
import { loadAll, saveAll } from "./store";
const main = async () => {
const existing = await loadAll();
console.log(`Loaded ${existing.length} bookmark(s).`);
// Add one
const fresh: Bookmark = typia.random<Bookmark>();
await saveAll([...existing, fresh]);
// Read again to verify the round-trip
const after = await loadAll();
console.log(`Now ${after.length}.`);
};
main().catch(console.error);Run it twice. The first time it creates bookmarks.json with one bookmark. The second time it loads that one, appends another, and writes both back — so the file grows by one entry per run.
npx ttsx src/04-store.ts
npx ttsx src/04-store.tsCorrupt the file on purpose
To see the safety net in action, open bookmarks.json and break one entry — for example, change the createdAt value to "yesterday" so the file looks like:
[
{
"id": "...",
"url": "https://...",
"title": "...",
"tags": ["..."],
"createdAt": "yesterday"
}
]Then re-run:
npx ttsx src/04-store.tsTypeGuardError: invalid type on $input[0].createdAt, expect to be string & Format<"date-time">assertParse refused to hand back a half-valid array. You can decide to fall back to [], log the corrupt record, prompt the user — but you never silently load broken data.
The variants you didn’t need today
| Function | When |
|---|---|
typia.json.isParse<T> | ”Try to parse, give me null if anything’s wrong” |
typia.json.validateParse<T> | ”Give me every error in bookmarks.json so I can report all of them at once” |
typia.json.isStringify<T> | Like assertStringify but returns null instead of throwing |
typia.json.validateStringify<T> | Validate, then serialize, returning IValidation<string> |
typia.json.stringify<T> | Fastest. Skips validation. Trust the caller. |
See json.parse and json.stringify for full signatures.
A note on Primitive<T>
If you look at the return type of assertParse<Bookmark[]>, it’s Primitive<Bookmark[]>. That’s typia’s “what survives a JSON round-trip” projection. For Bookmark it’s identical to Bookmark (nothing in it is Date or bigint or Set), so you can ignore the distinction.
If your type does contain a Date, Primitive<T> rewrites that property to string & tags.Format<"date-time"> — because that’s what JSON.stringify(date) produces and what JSON.parse gives you back. Same for Map, Set, and friends collapsing to plain objects. The Primitive type tells you about a few classes of “this won’t survive JSON” mistakes at compile time. See json.parse > Primitive for the details.
What you’ve done
- Saved typed data to disk with
assertStringify - Loaded it back with
assertParseand watched it refuse corrupt input - Picked the right variant for each situation
Next we’ll do something more ambitious: hand the bookmark service to an LLM and let it create bookmarks from natural language.