Skip to main content

JS Maps and Objects as Keys

Evan McDowell

Evan McDowell

Software Engineer @ Ware2go

What is a Map#

A Map is a data structure in JavaScript that is similar to an object, with some main differences being:

  • A Map remembers the original insertion order of the keys
  • Any value (both objects and primitive values) may be used as either a key or a value.
  • Maps are itereable (similar to arrays) and has size property (similar to array length)
  • Performs better in scenarios involving frequent additions and removals of key-value pairs than an object.

OK now we got that out of the way, we can do some exploring into this Object as keys thing MDN is talking about. If you're like me you like to experiment from time to time with code just to see if things will work. I am a firm believer that experimentation leads to a better understanding regardless if you succeed or not. In this instance learning about how we can use objects as keys with Maps.

So before get to Maps we need a base understanding about objects, specifically how the equallity operator handles them. Feel free to pop open chrome console and follow along.

Object equality and references#

So lets talk about object equality in JavaScript. If we define two objects with the same keys and values:

const obj1 = { key: "value" };
const obj2 = { key: "value" };

And we use === triple equals as our equality operator to compare the two objects

obj1 === obj2;

What would we expect the outcome to be? If you said false you're correct. While you may say thats weird, I can explain why this is. JavaScript is sort of lazy in comparing objects. What I mean is that JavaScript only looks at the object reference when comparing them.

What is a reference#

If you have ever named a variable in code, then you have made use of a reference. To take a slightly deeper dive when something gets assigned it also gets written to computor memory. You can think of computer memory as a table that stores values at addresses. Much like a warehouse stores packages on shelves that have addresses on them, so that they know where to find things. So one of our objects may look like this in memory.

addressvalue
0012CCGWH80{ key: "value" }

With the address being some hash value. Now since we named this variable we have an identifier, a name that points to this address. A pointer if you will.

addressvalue
obj1 ->0012CCGWH80{ key: "value" }

So now the user (coder) can reference values in memory through a name.

Keep this in mind as we move along, it will come back up.

Maps#

So lets play around with Maps and object keys. We can instanciate a Map with an array of arrays, with the inner beeeing key value pairs.

const objMap = new Map([[obj1, "value"]]);

So in the above example the initializer value being [[key, value]]. This means obj1 becomes the key in the Map. If you're following along in the console, when you print objMap you will see something like this:

The PROBLEM#

So with the object reference knowlege we gained earlier, what would we expect the output to be for this get call to the Map we created:

objMap.get({ key: "value" });

If you said undefined, you are most certainly correct. Because we learned earlier that JavaScript looks at the reference to the object, not the actual deep equality of the object. So since we passed an unnamed object here, (even though it is the same object) it has a different reference than the object key stored in the Map.

So if we used the name of the key we created instead, this will work because the equality passes (becausee they share the same reference).

objMap.get(obj1);

And you should be successful in retreiving "value".

One Solution#

One solution would be to use hashing, not going to get into that right now though, but if you're interested here might be a good place to start.


Lets Experiment#

Consider a Theme object, defined in an aplication, a constant object that will always have a reference.

const THEME = {
colors: {
neutrals: {
brandLight: {
hex: "#fff",
name: "white",
tag: "body",
},
brandMedium: {
hex: "#808080",
name: "grey",
tag: "header",
},
},
},
fonts: {
newTimes: {
roman: "roman",
},
},
};

Theme contains some colors, grouped by catagory and we have another object called TAGS which contains where color catagorys are used.

const TAGS = {
colors: {
neutrals: ["header", "body"],
},
};

Lets create a Map that maps specific colors (as objects defined with hex and other info), to that colors categorical tags.

const colorToColorCategoryTags = new Map([
[THEME.colors.neutrals.brandLight, THEME.tags.colors.neutrals],
[THEME.colors.neutrals.brandMedium, THEME.tags.colors.neutrals],
]);

If you notice that the named brand colors are objects, used as keys, and thee values are the array of tags used by the color category. So we can use this map like so (with named references):

colorToColorCategoryTags.get(THEME.colors.neutrals.brandLight);

Which will return our array of tags.

Conclusion#

You may ask why would you do this, well you most likely won't. BUT its always good to know what the technology you use can do, and what you can do with your technology. To get a deeper understanding of things, I will always be in favor of experiementation. This is how people find neat and easier ways to do things.

Ofcourse a more practical example of a Map use would be like a filter on a table, something you add and remove keys on, here it would have better preformance than a vanilla object.

Obviously there are many different ways to do things with code, and personally if I wanted to get tags of color categorys like our example here, I dont know if a Map would be my go-to solution. But it is a clean readable way to do it, which in my book counts for most of its score points on the "Would I use this" scale.


Sources:

Create Your Own Doc io

Evan McDowell

Evan McDowell

Software Engineer @ Ware2go

I know this is kindof meta putting this on a Docusaurus io page, but im going to walk through how to set up your own personal io page with Docusaurus and setup github actions and gh-pages to deploy it to your personal io page.

So Docusaursus is pretty awesome and you can deploy it to your own gh-pages, but ill go through some of the troubles and pitfalls i had.

Initialising#

So if you go to docusaurus theye have an awesome intro read me, and to start a fresh repo its super simple, just use the command

npx @docusaurus/init@latest init [name] [template] Name is the name you want your repo and for template i just went withclassic

Note that this command creates a folder and structure in the directory you are currently in (in your terminal) so one way to create a new repo is, create one in github, run this command locally and git init inside the folder it creates and set origin to the repo you created in git. ex:

git remote add origin https://github.com/[UserName]/[repoName].git

commit files, then push up your repo. After that you should see your files on github.

Customizing#

From here you can edit the docusaurus.config.js file. We should change the url, base url (if needed), project name to line up with the repo name. If its a personal repo your url should be https://github.com/[UserName] and base url (if using io for repo) should be /[RepoName]/. You can also customise other things to your hearts content, like your own links etc.

Automating Deployments#

So the doc i was following (which was pretty good) triggering-deployment-with-github-actions takes you through everything with links to github documentation on how to do things, overall excelent doc, and would suggest following along with me there, i will basically just add some tidbits to this docs flow.

Some of the imidiate things you will see if you are using a Mac is that you may not have xclip for coppying the ssh keys from your terminal to your clipboard. A good alt for this would be pbcopy. So for example you can substitute

pbcopy < ~/.ssh/id_rsa.pub

for

xclip -sel clip < ~/.ssh/id_rsa.pub

So for the first step it links you to github how to create a new ssh key. For the github actions we want to use thee rsa algorythm. so use

ssh-keygen -t rsa -b 4096 -C "your_email@example.com" with the email being the email associated with the repo/account aka yours.

This will bring up a prompt in your terminal asking where you want to save the key, usually ~/.ssh/, but if you already have an id_rsa you can name it something else, just make sure to use that when entering it into github. Next it will ask for a passphrase, just hit enter (no passphrase), alternatively you could add -N "" to the ssh-keygen command for empty passphrase.

Step 2: Deploy Keys they start you with that link, you can scroll down to step 4 on that page if you are starting on your repo page. Settings -> Deploy keys -> Add Key

I dont think the name matters here but use the pbcopy command to copy the ssh key created earlier onto clipboard so you can paste it in as the key here (in github). make sure to check the checkbox 'Allow write access'.

Step 3: Secrets. Basically the same as deploy keys except under seecret, and using the id_rsa instead or the id_rsa.pub (private key compared to public key). But the name here is important GH_PAGES_DEPLOY, this corresponds with the secret name used in ./github/workflows/documentation.yml line 35. (which is the next step)

Step 4: Documentation.yml file. Stock out of the box this sets up deployment from branch called documentation, but you can edit this to be wichever branch you like. As well as editing the run commands for example if you have nested projects in a monoRepo (if the yarn locks dont conflict or reference a private npm directory that you need tokens for). So if its your personal github you shouldnt need to worry about that, it should just work based on what branch you tell it to build off of.

May have skipped this step, but you need to configure your gh pages under the pages tab in the repo settings. It needs to be configured to gh-pages branch (if theres no one you can make one and push it up). i think if you make one and push it up the pages tab should prefill with gh-pages being the pages target branch.

NOTE: Dont hit choose a theme because then you url will change to a random generated url.

Conclusion#

Thats pretty much it, so whenever you make a PR or push to the branch you specified (documentation if you didnt change it) the github action should build and deploy it to the gh-pages branch and url. I like to put that url in the repo read me, just so i can just click it when i want to see it.

And thats it! Hope this is a helpful walkthrough, hope you can avoid those pitfalls as well.


sources: