Maps
Maps — sometimes called associative arrays, hashes, or dictionaries in other programming languages, store a collection of key-value pairings. Maps are constructed as a comma-separated list of key-value pairs enclosed by curly braces. Each key-value pair uses a colon to differentiate between the key and the value.
{
"name": "Ghost",
"value": 57.3,
"handler": function(x) { return x * x }
}
A key written as a bare identifier is taken as a string, so these two are the same map:
{ "name": "Ghost" }
{ name: "Ghost" }
Strings, numbers, and booleans can all be used as keys.
When a value is already in a variable of the same name as the key, the key can be left off entirely — the shorthand form:
name = "Ghost"
version = "1.0.0-beta.3"
console.log({ name, version })
// >> {name: Ghost, version: 1.0.0-beta.3}
Shorthand and ordinary pairs mix freely in one literal: { name, count: 2 }. The same shorthand works in reverse when destructuring a map.
Order
A map remembers the order its keys were first inserted, and every operation that walks it — for ... in, keys(), values(), entries() — reports that order.
scores = { zebra: 1, apple: 2 }
scores.set("mango", 3)
console.log(scores.keys()) // >> [zebra, apple, mango]
Assigning to a key that already exists updates its value and leaves its position alone. remove() drops a key out of the order entirely, leaving no gap behind. A key written twice in one literal keeps the position of its first appearance and the value of its last, the same rule assignment follows.
Two things cannot carry an order through, because there is none for them to carry:
json.encode()writes a map's keys alphabetically whatever order they went in, andjson.decode()cannot recover the order a JSON text was written in. A decoded map still has a fixed, repeatable order, just not the document's.- A map built by a Go program embedding Ghost starts life as a Go map, which has no order of its own. It settles into one and keeps it from then on.
Accessing Elements
You can access any element in a map by calling the subscript operator on it with the key of the element you want.
people = { Artemis: 35, Rabbit: 37, Orion: 43 }
console.log(people["Artemis"]) // >> 35
console.log(people["Rabbit"]) // >> 37
console.log(people["Orion"]) // >> 43
A key that is a valid identifier can also be reached with a .:
console.log(people.Artemis) // >> 35
Calling a key that does not exist will return a null value.
console.log(people["Arasaka"]) // >> null
Adding and Changing Elements
Assigning through either form adds the key if it isn't there, and replaces the value if it is. Maps are mutable and are held by reference, so a map passed into a function and changed there stays changed afterwards — which makes a map the usual home for state a function needs to update.
people["Arasaka"] = 12
people.Orion = 44
console.log(people["Arasaka"]) // >> 12
console.log(people.Orion) // >> 44
Iterating
for ... in walks a map. With one name you get each value; with two, the key and the value.
for (value in people) {
console.log(value)
}
for (name, age in people) {
console.log(name + " is " + age.toString())
}
Both forms walk the map in the order its keys were first inserted, the same order keys(), values(), and entries() report.
To walk it in some other order, sort the keys yourself:
for (name in people.keys().sort()) {
console.log(name)
}
Nesting
Values can be maps themselves, and the access forms chain:
config = {
window: { width: 1280, height: 720 },
title: "Ghost"
}
console.log(config.window.width) // >> 1280
Methods
entries()
The entries method returns the map as a list of [key, value] pairs, in insertion order — the counterpart to keys() and values() when you want both at once:
{ a: 1, b: 2 }.entries()
// [[a, 1], [b, 2]]
get()
The get method reads a key, and takes an optional fallback for when the key is absent. Without one, a missing key answers null — the same as [] indexing.
scores = { a: 1 }
console.log(scores.get("a")) // >> 1
console.log(scores.get("z")) // >> null
console.log(scores.get("z", 0)) // >> 0
has()
The has method reports whether a key is present, whatever its value. This is what distinguishes "absent" from "present and null", which reading the key cannot tell you:
scores = { a: 1, b: null }
console.log(scores.has("a")) // >> true
console.log(scores.has("b")) // >> true
console.log(scores.has("z")) // >> false
keys()
The keys method returns the map's keys as a list:
{ a: 1, b: 2 }.keys()
// [a, b]
length()
The length method returns how many pairs the map holds:
{ a: 1, b: 2 }.length()
// 2
merge()
The merge method returns a new map holding both maps' pairs, leaving each alone. Where both have the same key, the argument's value wins, the same rule a later assignment to that key would follow, but the key keeps the position it had in the receiver. The receiver's pairs come first, then whatever the argument adds:
defaults = { width: 800, height: 600 }
defaults.merge({ width: 1280 })
// { width: 1280, height: 600 }
remove()
The remove method deletes a key and answers the value that was stored under it. A key that was not there answers null rather than erroring — the same leniency reading a missing key has. It mutates the map:
scores = { a: 1, b: 2 }
console.log(scores.remove("a")) // >> 1
console.log(scores.remove("z")) // >> null
console.log(scores.keys()) // >> [b]
The removed key leaves no gap in the map's order.
set()
The set method assigns a key and returns the map itself, so calls can be chained. It mutates the map, exactly as assigning through [] or . does:
config = {}
config.set("width", 1280).set("height", 720)
console.log(config.width) // >> 1280
values()
The values method returns the map's values as a list:
{ a: 1, b: 2 }.values()
// [1, 2]
A map's values() line up with its keys() — both report insertion order, so the same position in each refers to the same pair.
forEach on a map. Use for ... in, which gives you the key and the value together.