Chris Padilla/Blog / Tech

Handling Dot Notation in MongoDB

There's quite a dance that needs to happen when sending data to a document in MongoDB.

Say that your document takes this shape:

import { z } from 'zod';

const userSchema = z.object({
  _id: z.string(),
  profile: UserProfileSchema,
  email: z.array(UserEmailSchema),
  calculated: CalculatedUserSchema.optional(),
  system: SystemUserSchema.optional()
});

const userProfileSchema = z.object({
  firstName: z.string().min(2).trim(),
  lastName: z.string().min(2).trim(),
  image: z.string().optional()
});

const userEmailSchema = z.object({
  address: z.string().email(),
  verified: z.boolean().default(false)
});

// etc...

Note that there is an email field with an array of objects.

If I wanted to update the address of the first email object in that array, my path object would look like this:

const patch = {
    'email.0.address': "hey@chris.com",
}

Here's what you get back from dirtyFields when you're using a form library on the client such as react-hook-form:

{
    email: [
        {
            address: address,
        }
    ]
}

That would essential replace the entire email field in the db.

There are a few steps to take to handle this:

First, let's pull in a library to handle converting the object to dot notation:

import { dot } from 'dot-object';

const dirtyFieldsDotNotation = dot(dirtyFields);
// {
//     "email[0].address": true
// }

Then, we can pass in the actual form value to the results object:

const updatedValues = dirtyFieldsKeys.reduce(
  (acc: Record<string, any>, key: string) => {
    const regex = new RegExp(/\[|(\]\.)/, 'g');
    const parsedKey = key.replaceAll(regex, '.');
    acc[parsedKey] = get(parsedUpdatedValues, key);
    return acc;
  },
  {}
);

// {
//     "email.0.address": hi@chris.net
// }

Here, I've added a step to replace all angle brackets [] with a period . to conform with the expected array access syntax for mongo.

With that, our object is now safe to send to our api and pass to our patch call to Mongo:

  const client = await clientPromise;
  const collection = client.db('project').collection('users');
  const result = await collection.updateOne(
    { _id: id },
    { $set: patch },
    { upsert: true }
  );