Skip to content

Instantly share code, notes, and snippets.

@Danetag
Created January 7, 2025 19:29
Show Gist options
  • Save Danetag/7300ad41c09d4bc731c6ea5d78d7038f to your computer and use it in GitHub Desktop.
Save Danetag/7300ad41c09d4bc731c6ea5d78d7038f to your computer and use it in GitHub Desktop.
import {describe, it, expect} from 'vitest';
import {merge} from './deepMerge';
describe('deepMerge', () => {
it('should merge flat objects', () => {
const obj1 = {a: 1, b: 2};
const obj2 = {c: 3, d: 4};
const result = merge({}, obj1, obj2);
expect(result).toEqual({
a: 1,
b: 2,
c: 3,
d: 4,
});
});
it('should override primitive values', () => {
const obj1 = {a: 1, b: 'old'};
const obj2 = {b: 'new'};
const result = merge({}, obj1, obj2);
expect(result).toEqual({
a: 1,
b: 'new',
});
});
it('should merge nested objects', () => {
const obj1 = {
a: {
b: 1,
c: 2,
},
};
const obj2 = {
a: {
c: 3,
d: 4,
},
};
const result = merge({}, obj1, obj2);
expect(result).toEqual({
a: {
b: 1,
c: 3,
d: 4,
},
});
});
it('should replace arrays instead of merging them', () => {
const obj1 = {
arr: [1, 2, 3],
};
const obj2 = {
arr: [4, 5, 6],
};
const result = merge({}, obj1, obj2);
expect(result).toEqual({
arr: [4, 5, 6],
});
});
it('should handle null values', () => {
const obj1 = {
a: null,
b: 1,
};
const obj2 = {
b: null,
};
const result = merge({}, obj1, obj2);
expect(result).toEqual({
a: null,
b: null,
});
});
it('should merge multiple objects', () => {
const obj1 = {a: 1};
const obj2 = {b: 2};
const obj3 = {c: 3};
const result = merge({}, obj1, obj2, obj3);
expect(result).toEqual({
a: 1,
b: 2,
c: 3,
});
});
it('should handle complex nested structures', () => {
const obj1 = {
config: {
api: {
endpoint: 'http://old.api.com',
timeout: 1000,
},
features: ['a', 'b'],
settings: {
theme: 'dark',
notifications: {
email: true,
push: false,
},
},
},
};
const obj2 = {
config: {
api: {
endpoint: 'http://new.api.com',
},
features: ['c'],
settings: {
notifications: {
push: true,
},
},
},
};
const result = merge({}, obj1, obj2);
expect(result).toEqual({
config: {
api: {
endpoint: 'http://new.api.com',
timeout: 1000,
},
features: ['c'],
settings: {
theme: 'dark',
notifications: {
email: true,
push: true,
},
},
},
});
});
it('should not modify source objects', () => {
const obj1 = {a: {b: 1}};
const obj2 = {a: {c: 2}};
const original1 = JSON.parse(JSON.stringify(obj1));
const original2 = JSON.parse(JSON.stringify(obj2));
merge({}, obj1, obj2);
expect(obj1).toEqual(original1);
expect(obj2).toEqual(original2);
});
it('should handle empty objects', () => {
const obj1 = {a: 1};
const obj2 = {};
const result = merge({}, obj1, obj2);
expect(result).toEqual({a: 1});
});
it('should handle undefined values', () => {
const obj1 = {a: undefined, b: 1};
const obj2 = {b: 2};
const result = merge({}, obj1, obj2);
expect(result).toEqual({a: undefined, b: 2});
});
});
/*
* A custom and simple `lodash/merge`, focused on deep merging JSON objects.
*/
type JsonPrimitive = string | number | boolean | null | undefined;
type JsonArray = JsonValue[];
type JsonObject = {[key: string]: JsonValue};
type JsonValue = JsonPrimitive | JsonObject | JsonArray;
/**
* Deep merges multiple JSON objects, preserving type information
*/
export function merge<First extends JsonObject, Rest extends JsonObject[]>(
first: First,
...rest: Rest
): First & UnionToIntersection<Rest[number]> {
const target = {...first} as JsonObject;
for (const source of rest) {
for (const key in source) {
const sourceValue = source[key];
const targetValue = target[key];
if (isObject(sourceValue) && isObject(targetValue)) {
target[key] = merge(targetValue, sourceValue);
} else if (Array.isArray(sourceValue)) {
target[key] = [...sourceValue];
} else {
target[key] = sourceValue;
}
}
}
return target as First & UnionToIntersection<Rest[number]>;
}
type UnionToIntersection<U> = (
U extends unknown ? (k: U) => void : never
) extends (k: infer I) => void
? I
: never;
function isObject(value: unknown): value is JsonObject {
return typeof value === 'object' && value !== null && !Array.isArray(value);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment