diff --git a/packages/change-case/src/keys.spec.ts b/packages/change-case/src/keys.spec.ts index c6ce1ee2..fcf77b77 100644 --- a/packages/change-case/src/keys.spec.ts +++ b/packages/change-case/src/keys.spec.ts @@ -71,4 +71,20 @@ describe("change keys", () => { expect(camelCase(input, depth, options)).toEqual(result); }); } + + it("passes non-plain object values through unchanged", () => { + const date = new Date("2020-01-02T03:04:05.000Z"); + const map = new Map([["a", 1]]); + const regexp = /foo/g; + const input = { created_at: date, some_map: map, some_regexp: regexp }; + + const result = camelCase(input, Infinity) as Record; + + expect(result.createdAt).toBe(date); + expect((result.createdAt as Date).toISOString()).toBe( + "2020-01-02T03:04:05.000Z", + ); + expect(result.someMap).toBe(map); + expect(result.someRegexp).toBe(regexp); + }); }); diff --git a/packages/change-case/src/keys.ts b/packages/change-case/src/keys.ts index f83ec699..d4e8f2d1 100644 --- a/packages/change-case/src/keys.ts +++ b/packages/change-case/src/keys.ts @@ -1,7 +1,14 @@ import * as changeCase from "./index.js"; -const isObject = (object: unknown) => - object !== null && typeof object === "object"; +// Only recurse into arrays and plain objects. Other objects (`Date`, `Map`, +// `Set`, `RegExp`, class instances, …) can rely on internal slots that a +// shallow key-renamed clone would not preserve, so they are passed through +// unchanged as values. +const isPlainObject = (object: unknown): boolean => { + if (object === null || typeof object !== "object") return false; + const prototype = Object.getPrototypeOf(object); + return prototype === null || prototype === Object.prototype; +}; function changeKeysFactory< Options extends changeCase.Options = changeCase.Options, @@ -13,12 +20,14 @@ function changeKeysFactory< depth = 1, options?: Options, ): unknown { - if (depth === 0 || !isObject(object)) return object; + if (depth === 0) return object; if (Array.isArray(object)) { return object.map((item) => changeKeys(item, depth - 1, options)); } + if (!isPlainObject(object)) return object; + const result: Record = Object.create( Object.getPrototypeOf(object), );