March 23, 2023

Checking object equality with value objects

In JavaScript, you can’t check if two objects are equal like this:

const point1 = { x: 2, y: 10 }
const point2 = { y: 10, x: 2 }

point1 === point2 // false

However, this will work:

const point1 = { x: 2, y: 10 }
const point2 = point1

point1 === point2 // true

The second example works because I’m checking the equality based on the identity of both objects—in other words, they are the same object.

The first one didn’t work because JavaScript see them as two different objects.

To make the first example work, you need to check it like this:

const point1 = { x: 2, y: 10 }
const point2 = { y: 10, x: 2 }

point1.x === point2.x && point1.y === point2.y // true

Writing this code every time we want to check if two points have the same values is tedious.

A better way to handle equality based on value between objects is to introduce a new Value Object for that data type.

Value Objects are simple immutable objects that wrap some value (primitive value or a record), to provide all the needed operations for that value—like checking equality or arithmetic operations (like addition or multiplication).

Creating a Value Object for the example above would look like this:

class Point {
  #x
  #y

  constructor(x, y) {
    this.#x = x
    this.#y = y
  }

  get x() {
    return this.#x
  }

  get y() {
    return this.#y
  }

  equals(otherPoint) {
    return this.#x === otherPoint.x && this.#y === otherPoint.y
  }
}

Now to check if two points are equal, I’ll write this:

const point1 = new Point(2, 10)
const point2 = new Point(2, 10)

point1.equals(point2) // true

Checking equality is not the only operation I can add there. Another good example is adding two Value Objects together.

class Point {
  //...

  add(anotherPoint) {
    return new Point(this.#x + anotherPoint.x, this.#y + anotherPoint.y)
  }
}

Notice how I returned a new instance of Point when adding a new point to it. I did this because Value Objects should be immutable, meaning you should not change its values directly and instead return a new object with the new value. This way, these value objects will be treated as true values that we can pass throughout the codebase without worrying about unexpected mutability issues.

Stay up-to-date on the latest projects and articles from me