JavaScript Zero to Advanced #6: Objects, Destructuring, Spread/Rest & JSON
Arrays are perfect for lists, but most data has shape: a user has a name, an email and a role; a product has a price and a stock count. In JavaScript that shape is described with objects. In this part you will learn how to create and work with objects, how to unpack them with destructuring, how to copy and merge them with spread, and how to send them over the network as JSON.
Creating and using objects
An object is a collection of key-value pairs (also called properties) written between curly braces:
const user = {
name: "Budi",
email: "budi@example.com",
age: 28,
isAdmin: false,
"favorite color": "blue"
}
console.log(user.name) // dot notation
console.log(user["email"]) // bracket notation
console.log(user["favorite color"]) // needed for keys with spaces
const field = "age"
console.log(user[field]) // dynamic key
user.age = 29 // update
user.city = "Jakarta" // add
delete user.isAdmin // remove
console.log(user)
console.log(user.phone) // missing property
Output:
Budi
budi@example.com
blue
28
{
name: 'Budi',
email: 'budi@example.com',
age: 29,
'favorite color': 'blue',
city: 'Jakarta'
}
undefined
Notice that user is a const, yet we changed its properties. const only prevents reassigning the variable to a different object; the object itself can still change.
Methods and this
A function stored in an object is called a method. Inside a regular method, this refers to the object the method was called on:
const account = {
owner: "Siti",
balance: 100000,
deposit(amount) {
this.balance += amount
return this.balance
},
describe() {
return `${this.owner} has Rp${this.balance}`
}
}
account.deposit(50000)
console.log(account.describe())
Output:
Siti has Rp150000
Avoid arrow functions for object methods that use this: arrow functions do not have their own this, so this.balance would not point to the object.
Shorthand, computed keys and looping
const name = "Andi"
const role = "editor"
const key = "lastLogin"
const member = { name, role, [key]: "2025-01-15" }
console.log(member)
console.log(Object.keys(member))
console.log(Object.values(member))
for (const [prop, value] of Object.entries(member)) {
console.log(`${prop} = ${value}`)
}
console.log("role" in member, Object.hasOwn(member, "email"))
Output:
{ name: 'Andi', role: 'editor', lastLogin: '2025-01-15' }
[ 'name', 'role', 'lastLogin' ]
[ 'Andi', 'editor', '2025-01-15' ]
name = Andi
role = editor
lastLogin = 2025-01-15
true false
When the variable name matches the key, you can write it once ({ name } instead of { name: name }). Object.entries turns an object into an array of [key, value] pairs, so all the array methods from Part 5 work on it.
Optional chaining
Reading a property of undefined crashes your program. The optional chaining operator ?. stops and returns undefined instead, and it pairs nicely with ?? from Part 2:
const order = { id: 1, customer: { name: "Dewi", address: null } }
console.log(order.customer.name)
console.log(order.customer.address?.city)
console.log(order.shipping?.courier ?? "Not assigned")
try {
console.log(order.customer.address.city)
} catch (error) {
console.log(error.constructor.name)
}
Output:
Dewi
undefined
Not assigned
TypeError
Destructuring
Destructuring lets you pull values out of objects and arrays into variables in a single line. You can rename, set defaults, and go into nested objects:
const product = {
title: "Mechanical Keyboard",
price: 750000,
specs: { switch: "brown", layout: "TKL" }
}
const { title, price, stock = 0 } = product
console.log(title, price, stock)
const { title: productName, specs: { layout } } = product
console.log(productName, layout)
// Array destructuring is based on position
const [first, second, , fourth] = ["a", "b", "c", "d"]
console.log(first, second, fourth)
// Swap two variables
let x = 1
let y = 2;
[x, y] = [y, x]
console.log(x, y)
Output:
Mechanical Keyboard 750000 0
Mechanical Keyboard TKL
a b d
2 1
Destructuring is especially nice in function parameters, because the caller can pass an options object in any order:
function createPost({ title, author = "Anonymous", published = false }) {
return `"${title}" by ${author} (${published ? "published" : "draft"})`
}
console.log(createPost({ title: "Hello World", author: "Siti" }))
console.log(createPost({ published: true, title: "JS Tips" }))
Output:
"Hello World" by Siti (draft)
"JS Tips" by Anonymous (published)
Spread and rest
The same three dots ... do two opposite jobs. Spread expands an array or object into individual items. Rest collects the remaining items into a new array or object.
// Spread with arrays
const frontend = ["HTML", "CSS"]
const skills = [...frontend, "JavaScript", "Node.js"]
console.log(skills)
console.log(Math.max(...[4, 9, 2]))
// Spread with objects: copy and override
const defaults = { theme: "light", language: "en", notifications: true }
const userSettings = { language: "id" }
const settings = { ...defaults, ...userSettings }
console.log(settings)
// Rest in destructuring
const { theme, ...others } = settings
console.log(theme, others)
// Rest parameters
function sum(...numbers) {
return numbers.reduce((total, n) => total + n, 0)
}
console.log(sum(1, 2, 3, 4))
Output:
[ 'HTML', 'CSS', 'JavaScript', 'Node.js' ]
9
{ theme: 'light', language: 'id', notifications: true }
light { language: 'id', notifications: true }
10
With object spread, later properties win, which makes it perfect for "defaults plus overrides".
The shallow copy trap
Spread only copies one level deep. Nested objects are still shared between the copy and the original:
const original = { name: "Budi", address: { city: "Bandung" } }
const copy = { ...original }
copy.name = "Siti"
copy.address.city = "Medan"
console.log(original.name, original.address.city)
const deep = structuredClone(original)
deep.address.city = "Bali"
console.log(original.address.city, deep.address.city)
Output:
Budi Medan
Medan Bali
Changing copy.name did not affect the original, but changing copy.address.city did, because both objects point to the same address object. Use structuredClone (available in modern browsers and Node.js 17+) when you need a full deep copy.
JSON: data for the network
JSON (JavaScript Object Notation) is a text format for exchanging data between servers and clients. It looks like JavaScript objects, but keys must be in double quotes and it cannot contain functions or undefined.
const profile = {
name: "Dewi",
age: 31,
hobbies: ["reading", "cycling"],
greet() { return "hi" },
nickname: undefined
}
const json = JSON.stringify(profile)
console.log(json)
console.log(typeof json)
console.log(JSON.stringify({ id: 7, tags: ["js"] }, null, 2))
const parsed = JSON.parse('{"name":"Rudi","active":true,"score":88}')
console.log(parsed.name, parsed.active, parsed.score + 2)
Output:
{"name":"Dewi","age":31,"hobbies":["reading","cycling"]}
string
{
"id": 7,
"tags": [
"js"
]
}
Rudi true 90
Notice that greet and nickname disappeared: JSON cannot represent functions or undefined. The third argument of JSON.stringify controls indentation, which is useful for logs and config files.
JSON.parse throws an error on invalid input, so wrap it in try/catch when the data comes from outside:
function safeParse(text) {
try {
return JSON.parse(text)
} catch {
return null
}
}
console.log(safeParse('{"ok":true}'))
console.log(safeParse("{ok: true}")) // keys without quotes are invalid JSON
Output:
{ ok: true }
null
Common mistakes
- Comparing objects with
===. Two objects with identical content are still different objects:{ a: 1 } === { a: 1 }isfalse. - Assuming spread makes a deep copy. Nested objects are shared; use
structuredClonewhen needed. - Arrow functions as methods. They do not get their own
this. - Destructuring
undefined.const { a } = undefinedthrows. Give parameters a default:function f({ a } = {}). - Forgetting that JSON is a string. You cannot read
json.nameuntil youJSON.parseit.
Exercise
Create an object config with default settings for an app (apiUrl: "https://api.example.com", timeout: 5000, retries: 3, features: { darkMode: false }). Write a function mergeConfig(overrides) that returns a new config without changing the original, correctly merging the nested features object as well. Convert the result to pretty-printed JSON, parse it back, and use destructuring to log timeout and darkMode.
Summary
- Objects store key-value pairs; access them with dot or bracket notation, and use methods with
this. Object.keys,Object.valuesandObject.entrieslet you loop through objects.?.and??make reading uncertain data safe.- Destructuring unpacks objects and arrays; spread copies and merges; rest collects the remainder.
- Spread is shallow;
structuredClonemakes deep copies. JSON.stringifyandJSON.parseconvert between objects and JSON text.
In Part 7 we bring JavaScript to the browser and make web pages interactive with the DOM and events.
Related articles
PostgreSQL Zero to Advanced #5: Aggregation, GROUP BY & HAVING
Turn raw orders into business numbers: total revenue, average order value, sales per month and top customers using COUNT, SUM, AVG, GROUP BY, HAVING and FILTER.
PHP Zero to Advanced #5: Arrays & Array Functions
Master PHP arrays: indexed and associative arrays, nested data, destructuring, and the essential functions array_map, array_filter, array_reduce, usort, and friends.
Ruby on Rails Zero to Advanced #5: Routing, Controllers & Views
Follow a request through Rails: define routes, use resources, read params in controllers, and render dynamic HTML with ERB views and partials.