Skip to content

Commit

Permalink
fix: only merge plain objects (#111)
Browse files Browse the repository at this point in the history
  • Loading branch information
pi0 authored Oct 24, 2023
1 parent 59d0f6a commit 4111333
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 6 deletions.
25 changes: 19 additions & 6 deletions src/defu.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
import type { Merger, DefuFn as DefuFunction, DefuInstance } from "./types";

function isObject(value: any) {
return value !== null && typeof value === "object";
}

// Base function to apply defaults
function _defu<T>(
baseObject: T,
defaults: any,
namespace = ".",
merger?: Merger,
): T {
if (!isObject(defaults)) {
if (!_isPlainObject(defaults)) {
return _defu(baseObject, {}, namespace, merger);
}

Expand All @@ -34,7 +30,7 @@ function _defu<T>(

if (Array.isArray(value) && Array.isArray(object[key])) {
object[key] = [...value, ...object[key]];
} else if (isObject(value) && isObject(object[key])) {
} else if (_isPlainObject(value) && _isPlainObject(object[key])) {
object[key] = _defu(
value,
object[key],
Expand All @@ -49,6 +45,23 @@ function _defu<T>(
return object;
}

// From sindresorhus/is-plain-obj
// MIT License
// Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
function _isPlainObject(value: unknown): boolean {
if (value === null || typeof value !== "object") {
return false;
}
const prototype = Object.getPrototypeOf(value);
return (
(prototype === null ||
prototype === Object.prototype ||
Object.getPrototypeOf(prototype) === null) &&
!(Symbol.toStringTag in value) &&
!(Symbol.iterator in value)
);
}

// Create defu wrapper with optional merger and multi arg support
export function createDefu(merger?: Merger): DefuFunction {
return (...arguments_) =>
Expand Down
16 changes: 16 additions & 0 deletions test/defu.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,22 @@ describe("defu", () => {
}>();
});

it.skip("should avoid merging objects with custom constructor", () => {
class Test {
// eslint-disable-next-line no-useless-constructor
constructor(public value: string) {}
}
const result = defu({ test: new Test("a") }, { test: new Test("b") });
expect(result).toEqual({ test: new Test("a") });
});

it.skip("should assign date properly", () => {
const date1 = new Date("2020-01-01");
const date2 = new Date("2020-01-02");
const result = defu({ date: date1 }, { date: date2 });
expect(result).toEqual({ date: date2 });
});

it("should correctly merge different object types", () => {
// eslint-disable-next-line unicorn/consistent-function-scoping
const fn = () => 42;
Expand Down

0 comments on commit 4111333

Please sign in to comment.