json1-2 Exercise 1: Make an Order Serialisable, Then Get the Types Back ======================================================================= Run with Node 24.18.0: node ex1.mjs --- ex1.mjs --- // Exercise 1: shape an object for JSON, then get the types back const order = { id: 1001n, customer: { name: 'Ada', email: 'ada@example.com', password: 'hunter2' }, placed: new Date(Date.UTC(2026, 9, 12, 8, 30)), tags: new Set(['rush', 'gift']), notes: undefined, discount: NaN, lines: [{ sku: 'A1', qty: 2 }, { sku: 'B2', qty: 1 }], }; console.log('1. plain stringify:'); try { JSON.stringify(order); } catch (e) { console.log(' threw ' + e.constructor.name + ': ' + e.message); } const forJson = (key, value) => { if (typeof value === 'bigint') return value.toString(); // BigInt would throw if (value instanceof Set) return [...value]; // Set would become {} if (Number.isNaN(value)) return null; // say it out loud (it was null anyway) if (key === 'password') return undefined; // never send it return value; }; const text = JSON.stringify(order, forJson, 2); console.log('\n2. with replacer:\n' + text); const revive = (key, value) => { if (key === 'placed') return new Date(value); if (key === 'id') return BigInt(value); if (key === 'tags') return new Set(value); return value; }; const back = JSON.parse(text, revive); console.log('\n3. revived:'); console.log(' id', back.id, '| placed is Date:', back.placed instanceof Date, back.placed.toISOString(), '| tags', back.tags); console.log(' same id :', back.id === order.id); console.log(' same date:', back.placed.getTime() === order.placed.getTime()); console.log(' same tags:', [...back.tags].join() === [...order.tags].join()); console.log(' password kept?', 'password' in back.customer, '| discount (was NaN):', back.discount, '| notes key present:', 'notes' in back); // control: without the revive function the types are NOT restored const plain = JSON.parse(text); console.log('\n4. control, parse without reviver:', typeof plain.id, plain.placed.constructor.name, Array.isArray(plain.tags)); Output: 1. plain stringify: threw TypeError: Do not know how to serialize a BigInt 2. with replacer: { "id": "1001", "customer": { "name": "Ada", "email": "ada@example.com" }, "placed": "2026-10-12T08:30:00.000Z", "tags": [ "rush", "gift" ], "discount": null, "lines": [ { "sku": "A1", "qty": 2 }, { "sku": "B2", "qty": 1 } ] } 3. revived: id 1001n | placed is Date: true 2026-10-12T08:30:00.000Z | tags Set(2) { 'rush', 'gift' } same id : true same date: true same tags: true password kept? false | discount (was NaN): null | notes key present: false 4. control, parse without reviver: string String true WHY THIS WORKS AS AN ANSWER --------------------------- Step 1 shows the failure first (the BigInt), so the replacer is solving a real problem. One replacer function handles three different problems: BigInt (would throw), Set (would silently become {}) and password (should never be sent), and the output confirms each: "id" is the string "1001", "tags" is an array, no password. The NaN already became null on its own; the replacer line for it only makes that explicit. The reviver then restores three types, and the three comparisons (same id, same date, same tags) are checked with real equality tests, not by eye (a Date is compared by getTime(), a Set by its contents, since two Sets are never === to each other). The control in step 4 shows the restoring is the reviver's work: without it the id is a string, the date is a String and the tags are an array. Two things are not recovered, by design: the password (deliberately dropped) and the original NaN (now null). Caveat: the reviver matches by key name at ANY depth; this order has one "id", so it is safe here only because of the sample (see the chapter's tip box).