Skip to content

Initial dictionary page #1006

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions data/sidebar_manual_v1200.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"tuple",
"record",
"object",
"dict",
"variant",
"polymorphic-variant",
"null-undefined-option",
Expand Down
51 changes: 51 additions & 0 deletions misc_docs/syntax/language_dict.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
---
id: "dict"
keywords: ["dict"]
name: "dict"
summary: "This is the `dict{}` syntax"
category: "languageconstructs"
---

> Available in v12+

The `dict{}` syntax is used to represent [dictionaries](dict.md). It's used both when creating dicts, and when pattern matching on dicts.

### Example

<CodeTab labels={["ReScript", "JS Output"]}>

```res
// Create a dict
let d = dict{"A": 5, "B": 6, "C": 7}

// Pattern match on the full dict
let b = switch d {
| dict{"B": b} => Some(b)
| _ => None
}

// Destructure the dict
let dict{"C": ?c} = d
```

```js
let d = {
A: 5,
B: 6,
C: 7
};

let b = d.B;

let b$1 = b !== undefined ? b : undefined;

let c = d.C;
```

</CodeTab>

### References

* [Dictionaries](/docs/manual/latest/dict)


161 changes: 161 additions & 0 deletions pages/docs/manual/v12.0.0/dict.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
---
title: "Dictionary"
description: "Dictionary data structure in ReScript"
canonical: "/docs/manual/v12.0.0/dict"
---

# Dictionary

ReScript has first class support for dictionaries. Dictionaries are mutable objects with string keys, where all values must have the same type. Dicts compile to regular JavaScript objects at runtime.

## Create

You can create a new dictionary in a few different ways, depending on your use case.

<CodeTab labels={["ReScript", "JS Output"]}>

```res prelude
// Using the first class dict syntax
let d = dict{"A": 5, "B": 6}

// Programatically via the standard library
let d2 = Dict.fromArray([("A", 5), ("B", 6)])
```

```js
let d = {
A: 5,
B: 6
};

let d2 = Object.fromEntries([
[
"A",
5
],
[
"B",
6
]
]);

```

</CodeTab>

A few things to note here:

* Using the first class `dict{}` syntax compiles cleanly to a JavaScript object directly
* Using `Dict.fromArray` is useful when you need to create a dictionary programatically

## Access

You can access values from a Dictionary either via the the standard library `Dict` module functions, or using pattern matching.

<CodeTab labels={["ReScript", "JS Output"]}>

```res prelude
let d = dict{"A": 5, "B": 6, "C": 7}

// Using `Dict.get`
let a = d->Dict.get("A")

// Switching on the full dict
let b = switch d {
| dict{"B": b} => Some(b)
| _ => None
}

// Destructuring
let dict{"C": ?c} = d
```

```js
let d = {
A: 5,
B: 6,
C: 7
};

let a = d["A"];

let b = d.B;

let b$1 = b !== undefined ? b : undefined;

let c = d.C;
```
</CodeTab>

> In the Destructuring example, we're using the `?` optional pattern match syntax to pull out the `C` key value as an optional, regardless of if the dict has it or not.

## Pattern matching
Dictionaries have first class support for pattern matching. Read more in the [dedicated guide on pattern matching and destructring in ReScript](pattern-matching-destructuring.md#match-on-dictionaries).

## Updating and setting values

You can set and update new values on your dictionary using the `Dict.set` function. All updates are mutable.

<CodeTab labels={["ReScript", "JS Output"]}>

```res prelude
let d = dict{"A": 5, "B": 6}

d->Dict.set("C", 7)
```

```js
let d = {
A: 5,
B: 6
};

d["C"] = 7;
```

</CodeTab>

## Advanced example: Pattern matching on JSON

JSON objects are represented as dictionaries (`dict<JSON.t>`). You can leverage that fact to decode JSON in a nice way, using only language features:

<CodeTab labels={["ReScript", "JS Output"]}>

```res prelude
type user = {
name: string,
email: string,
}

/** Decode JSON to a `user`.
let decodeUser = (json: JSON.t) => {
switch json {
| Object(dict{"name": JSON.String(name), "email": JSON.String(email)}) =>
Some({name, email})
| _ => None
}
}

```

```js
function decodeUser(json) {
if (typeof json !== "object" || json === null || Array.isArray(json)) {
return;
}
let name = json.name;
if (typeof name !== "string") {
return;
}
let email = json.email;
if (typeof email === "string") {
return {
name: name,
email: email
};
}

}
```

</CodeTab>
57 changes: 57 additions & 0 deletions pages/docs/manual/v12.0.0/pattern-matching-destructuring.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -602,6 +602,63 @@ printStudents({

</CodeTab>

### Match on Dictionaries

You can pattern match on dictionaries just like you can on other ReScript data structures.

When pattern matching on a dictionary it's assumed by default that you're expecting the keys you match on to exist in the dictionary. Example:
<CodeTab labels={["ReScript", "JS Output"]}>

```res prelude
let d = dict{"A": 5, "B": 6}

// We're expecting the `B` key to exist below, and `b` will be `int` in the match branch
let b = switch d {
| dict{"B": b} => Some(b)
| _ => None
}
```

```js
let d = {
A: 5,
B: 6
};

let b = d.B;

let b$1 = b !== undefined ? b : undefined;
```

</CodeTab>

However, there are situations where you want to pull out the value of a key as an option. You can do that using the `?` optional syntax in the pattern match:

<CodeTab labels={["ReScript", "JS Output"]}>

```res prelude
let d = dict{"A": 5, "B": 6}

// We're pulling out `B` regardless of if it has a value or not, and therefore get `b` as `option<int>`
let b = switch d {
| dict{"B": ?b} => b
}
```

```js
let d = {
A: 5,
B: 6
};

let b = d.B;
```

</CodeTab>

Notice how in the first case, when not using `?`, we had to supply a catch-all case `_`. That's because the pattern match _expects_ `B` to exist in the first case, for the pattern to match. If `B` doesn't exist, the match falls through to the next branch, and therefore we need to catch it to be exhaustive in our matching.

However, in the second case, we don't need a catch-all case. That's because the first branch will _always_ match the dictionary - either `B` exists or it doesn't, but it doesn't matter because we're pulling it out as an optional value.

### Small Pitfall

Expand Down