Skip to content

Commit

Permalink
feat(isDeepEqual): add support for maps (#637)
Browse files Browse the repository at this point in the history
Add support for maps for the isDeepEqual function

Fixes: #626

---------

Co-authored-by: Eran Hirsch <eranhirsch@gmail.com>
  • Loading branch information
clemgbld and eranhirsch committed Apr 14, 2024
1 parent d3e004c commit c5e47a5
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 6 deletions.
1 change: 1 addition & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,7 @@ export default config(
"IArguments",
"Iterable",
"Promise",
"ReadonlyMap",
"RegExp",
],
},
Expand Down
63 changes: 63 additions & 0 deletions src/isDeepEqual.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,69 @@ describe("arrays", () => {
});
});

describe("Maps", () => {
it("works on shallow equal maps", () => {
expect(isDeepEqual(new Map([["a", 1]]), new Map([["a", 1]]))).toBe(true);
});

test("two empty Maps should be equal", () => {
expect(isDeepEqual(new Map(), new Map())).toBe(true);
});

it("two Maps with different size should not be equal", () => {
expect(isDeepEqual(new Map(), new Map([["a", 1]]))).toBe(false);
});

it("two Maps with different keys shoud not be equal", () => {
expect(
isDeepEqual(
new Map([
["a", 1],
["c", 2],
]),
new Map([
["a", 1],
["b", 2],
]),
),
).toBe(false);
});

it("two maps with the same keys but with different values should not be equal", () => {
expect(
isDeepEqual(
new Map([
["a", 1],
["b", 3],
["c", 2],
]),
new Map([
["a", 1],
["b", 2],
["c", 2],
]),
),
).toBe(false);
});

it("two Maps with the same non primitives data should be equal", () => {
expect(
isDeepEqual(
new Map([
["a", { a: [1, 2, 3] }],
["b", { a: [3] }],
["c", { b: [4] }],
]),
new Map([
["a", { a: [1, 2, 3] }],
["b", { a: [3] }],
["c", { b: [4] }],
]),
),
).toBe(true);
});
});

describe("Date objects", () => {
test("equal date objects", () => {
expect(
Expand Down
40 changes: 34 additions & 6 deletions src/isDeepEqual.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import { purry } from "./purry";
* objects all props will be compared recursively. The built-in Date and RegExp
* are special-cased and will be compared by their values.
*
* !IMPORTANT: Maps, Sets and TypedArrays, and symbol properties of objects are
* not supported right now and might result in unexpected behavior. Please open
* an issue in the Remeda github project if you need support for these types.
* !IMPORTANT: Sets, TypedArrays, and symbol properties of objects are not
* supported right now and might result in unexpected behavior. Please open an
* issue in the Remeda github project if you need support for these types.
*
* The result would be narrowed to the second value so that the function can be
* used as a type guard.
Expand Down Expand Up @@ -38,9 +38,9 @@ export function isDeepEqual<T, S extends T = T>(data: T, other: S): boolean;
* objects all props will be compared recursively. The built-in Date and RegExp
* are special-cased and will be compared by their values.
*
* !IMPORTANT: Maps, Sets and TypedArrays, and symbol properties of objects are
* not supported right now and might result in unexpected behavior. Please open
* an issue in the Remeda github project if you need support for these types.
* !IMPORTANT: Sets, TypedArrays, and symbol properties of objects are not
* supported right now and might result in unexpected behavior. Please open an
* issue in the Remeda github project if you need support for these types.
*
* The result would be narrowed to the second value so that the function can be
* used as a type guard.
Expand Down Expand Up @@ -121,6 +121,10 @@ function isDeepEqualImplementation<T, S>(data: S | T, other: S): data is S {
return data.toString() === (other as unknown as RegExp).toString();
}

if (data instanceof Map) {
return isDeepEqualMaps(data, other as unknown as Map<unknown, unknown>);
}

// At this point we only know that the 2 objects share a prototype and are not
// any of the previous types. They could be plain objects (Object.prototype),
// they could be classes, they could be other built-ins, or they could be
Expand All @@ -146,3 +150,27 @@ function isDeepEqualImplementation<T, S>(data: S | T, other: S): data is S {

return true;
}

function isDeepEqualMaps(
data: ReadonlyMap<unknown, unknown>,
other: ReadonlyMap<unknown, unknown>,
): boolean {
if (data.size !== other.size) {
return false;
}

// TODO: Once we bump our typescript target we can iterate over the map keys and values directly.
const keys = Array.from(data.keys());

for (const key of keys) {
if (!other.has(key)) {
return false;
}

if (!isDeepEqualImplementation(data.get(key), other.get(key))) {
return false;
}
}

return true;
}

0 comments on commit c5e47a5

Please sign in to comment.