Chapter 6.5☕ 19 min read

JSON, Set, Map & WeakMap

Object keys sirf strings hote hain — kya kabhi socha? Map koi bhi key type allow karta hai, WeakMap GC ka bestie hai.

01JSON: Data Exchange Raja

JSON (JavaScript Object Notation) is the universal data exchange format. Every API, every config file, every database speaks JSON. But it has strict rules about what it supports.

JSON only supports: strings, numbers, booleans, null, arrays, and objects. JSON does NOT support: undefined, functions, Dates (becomes string), BigInt (throws TypeError), Symbols. If you try to stringify a Date, it becomes a string. If you try to stringify undefined or a function, the property is silently omitted.
const data = { name: 'Sai', active: true, score: 99.5 };

// Stringify (JS → JSON string)
const jsonStr = JSON.stringify(data);
console.log(jsonStr); // '{"name":"Sai","active":true,"score":99.5}'

// Parse (JSON string → JS)
const parsed = JSON.parse(jsonStr);

// Pretty print with spaces
console.log(JSON.stringify(data, null, 2));

// The Date problem
const withDate = { created: new Date() };
const dateJson = JSON.stringify(withDate);
console.log(dateJson); // {"created":"2024-01-01T12:00:00.000Z"} (String!)
const back = JSON.parse(dateJson);
console.log(typeof back.created); // 'string' — Date is no longer a Date!

// Reviver to fix Dates
const fixed = JSON.parse(dateJson, (key, value) => {
  if (key === 'created') return new Date(value);
  return value;
});
console.log(fixed.created instanceof Date); // true ✅

The replacer function/array in JSON.stringify lets you filter or transform values before serialization. The reviver function in JSON.parse lets you transform values during deserialization — like resurrecting Date objects from strings.

02Set: Unique Values, SameValueZero

Set is a collection of unique values. It automatically removes duplicates using the SameValueZero algorithm, which is slightly different from ===.

// Create and add
const uniqueIds = new Set();
uniqueIds.add(1);
uniqueIds.add(2);
uniqueIds.add(2); // Ignored!
console.log(uniqueIds.size); // 2

// Initialize from array (remove duplicates)
const nums = [1, 2, 2, 3, 3, 3];
const unique = [...new Set(nums)]; // [1, 2, 3]

// SameValueZero: NaN equals NaN
const special = new Set();
special.add(NaN);
special.add(NaN); // Ignored!
console.log(special.has(NaN)); // true ✅ (unlike ===)

// Object reference trap
const objSet = new Set();
objSet.add({ id: 1 }); // Object A
objSet.add({ id: 1 }); // Object B (different reference!)
console.log(objSet.size); // 2 — they are different objects!

// To make it 1, use the SAME reference
const objA = { id: 1 };
objSet.add(objA);
objSet.add(objA); // Ignored
console.log(objSet.size); // Still 3 (A, B, objA)
Key methods: .add(value), .has(value), .delete(value), .clear(), .size. Set is iterable in insertion order. The most common use case is removing duplicates from an array: [...new Set(arr)]. Remember: objects are compared by reference, not by value — two objects with identical properties are still different entries in a Set.
03Map: Any Key Type, Ordered Pairs

Map is a key-value collection where keys can be any type — not just strings like plain objects. This is the fix for the infamous [object Object] key problem.

// The Object Key Problem
const obj = {};
const key1 = { id: 1 };
const key2 = { id: 2 };
obj[key1] = 'value 1';
obj[key2] = 'value 2'; // Overwrites value 1!
// Both keys became "[object Object]"
console.log(obj); // { "[object Object]": "value 2" }

// Map solves this
const map = new Map();
map.set(key1, 'value 1');
map.set(key2, 'value 2');
console.log(map.get(key1)); // 'value 1' ✅
console.log(map.size);      // 2

// Map accepts any key type
map.set(1, 'number key');
map.set(true, 'boolean key');
map.set(null, 'null key');

// Iteration (insertion order)
for (const [key, val] of map) {
  console.log(key, val);
}

// Convert Map to Object and vice versa
const objFromMap = Object.fromEntries(map);
const mapFromObj = new Map(Object.entries({ a: 1, b: 2 }));
V8 implements Map and Set using an 'ordered hash table'. This is a hash table that also maintains a doubly-linked list through all entries. This uses ~30% more memory than a plain hash table but guarantees that iteration happens in insertion order, which the ES6 spec requires.
04WeakMap & WeakSet: GC's Best Friends

WeakMap and WeakSet are like Map and Set, but with one critical difference: keys (for WeakMap) or values (for WeakSet) are held weakly. This means if the only reference to the key object is inside the WeakMap, the garbage collector can reclaim it.

// WeakMap for private data
const privateData = new WeakMap();

class User {
  constructor(name) {
    this.name = name;
    privateData.set(this, { loginCount: 0 }); // Private!
  }

  login() {
    const data = privateData.get(this);
    data.loginCount++;
    console.log(this.name + ' logged in ' + data.loginCount + ' times');
  }
}

const sai = new User('Sai');
sai.login(); // Sai logged in 1 times
// sai.privateData -> undefined (truly private)

// When 'sai' is set to null, the WeakMap entry is garbage collected
// No memory leak!
// WeakSet for tracking processed items
const processed = new WeakSet();

function processItem(item) {
  if (processed.has(item)) {
    console.log('Already processed!');
    return;
  }
  // ... process item ...
  processed.add(item);
}
WeakMap/WeakSet are NOT iterable: No .keys(), .values(), .forEach(), or .size. Why? Because entries can disappear at any time when GC runs. This makes iteration non-deterministic, so the spec disallows it. Use cases: private data for class instances, caching computed values, DOM node metadata without causing memory leaks.
05SameValueZero vs === vs Object.is

JavaScript has three different equality algorithms, and they behave differently with edge cases like NaN and -0. Understanding which one Map/Set uses is crucial.

// Strict Equality (===)
console.log(NaN === NaN); // false ❌
console.log(-0 === +0);   // true

// Object.is
console.log(Object.is(NaN, NaN)); // true ✅
console.log(Object.is(-0, +0));   // false (they are different!)

// SameValueZero (Map/Set behavior)
// Simulated:
function sameValueZero(x, y) {
  if (typeof x === 'number' && typeof y === 'number') {
    // x is NaN AND y is NaN
    if (x !== x && y !== y) return true; // NaN check
    // +0 and -0 are treated as equal
    if (Object.is(x, y)) return true;
    return x === y;
  }
  return Object.is(x, y);
}

// Why it matters for Map
const map = new Map();
map.set(NaN, 'Not a Number');
console.log(map.get(NaN)); // 'Not a Number' ✅ (NaN works as a key!)

map.set(-0, 'Negative Zero');
console.log(map.get(+0)); // 'Negative Zero' (-0 and +0 are same key)
📋 Why this matters:
• Map and Set using SameValueZero is a huge quality-of-life improvement. In plain objects, using NaN as a key doesn't work as expected because obj[NaN] converts to obj["NaN"], which is just a string. In Map, NaN is truly recognized as NaN.
• The ONLY difference between SameValueZero and Object.is is the -0/+0 behavior.

Lo kar liya — Key Points:

  • ✅ JSON.stringify converts Dates to strings and drops undefined/functions; use a reviver to restore types
  • ✅ Set stores unique values using SameValueZero (NaN equals NaN); objects are unique by reference, not value
  • ✅ Map allows ANY key type (objects, functions, primitives) and maintains insertion order
  • ✅ Plain objects coerce keys to strings ([object Object]); Maps do not
  • ✅ WeakMap/WeakSet hold keys weakly — if the key object is garbage collected, the entry disappears
  • ✅ SameValueZero (Map/Set) treats NaN === NaN as true and -0 === +0 as true; Object.is treats -0 and +0 as different
Course Search
Search across all chapters & stages
📖

Search the course

Type any topic — branching, stash, rebase, hooks — and jump straight to that chapter.

merge branchesgit stashundo commitrebase