Skip to content

Commit 58e97d1

Browse files
authored
Merge pull request #10 from panates/dev
Dev
2 parents 022e96b + ce6a59f commit 58e97d1

File tree

11 files changed

+1105
-564
lines changed

11 files changed

+1105
-564
lines changed

CHANGELOG.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
## Changelog
22

3-
### [v2.1.1](https://github.com/panates/jsopen-objects/compare/v2.1.0...v2.1.1) -
3+
### [v2.2.0](https://github.com/panates/jsopen-objects/compare/v2.1.1...v2.2.0) -
44

55
#### 🪲 Fixes
66

7-
- fix: Fixed "keepExisting" with "deep" option do not overwrite existing @Eray Hanoğlu
7+
- fix: Fixed array merge issue @Eray Hanoğlu
8+
9+
#### 💬 General Changes
10+
11+
- doc: Added documentation @Eray Hanoğlu

README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,24 @@
77

88
A 'swiss army knife' solution for working with javascript objects.
99

10+
## Functions
11+
12+
### [merge](docs/merge.md)
13+
Is a powerful, flexible tool for merging objects, arrays, and their nested properties.
14+
15+
### [clone / deepClone](docs/clone.md)
16+
Easy ways to create shallow or deep copies of objects and arrays.
17+
18+
### [omit / omitUndefined / omitNull / omitNullish](docs/omit.md)
19+
Easily exclude specific keys or nullish values from objects.
20+
21+
### [updateErrorMessage](docs/update-error-message.md)
22+
Update an Error object's message while correctly refreshing the stack trace.
23+
24+
### [Utilities](docs/utils.md)
25+
Various utility functions for object and type checking.
26+
27+
1028
## Installation
1129

1230
`$ npm install @jsopen/objects`

docs/clone.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# clone / deepClone
2+
3+
The `clone` and `deepClone` functions provide easy ways to create copies of objects and arrays. They are built on top of the [`merge`](merge.md) function and support all its powerful options.
4+
5+
## Functions
6+
7+
### `clone(obj, options?)`
8+
9+
Creates a copy of the given object. By default, it performs a **deep clone** of plain objects and arrays.
10+
11+
- **Default behavior**: Unlike `merge`, `clone` sets `deep: true` by default.
12+
- **Plain Objects/Arrays**: Recursively clones nested plain objects and arrays.
13+
- **Class Instances**: Non-plain objects (class instances) are copied by reference unless `deep: 'full'` is passed in options.
14+
15+
```typescript
16+
import { clone } from '@jsopen/objects';
17+
18+
const original = { a: 1, b: { c: 2 } };
19+
const copy = clone(original);
20+
21+
copy.b.c = 3;
22+
console.log(original.b.c); // 2 (original is unchanged)
23+
```
24+
25+
### `deepClone(obj, options?)`
26+
27+
Creates a **full deep copy** of the given object, including class instances.
28+
29+
- **Behavior**: Sets `deep: 'full'` by default.
30+
- **All Objects**: Recursively clones all objects, including class instances (excluding built-in types like `Date`, `RegExp`, etc.).
31+
32+
```typescript
33+
import { deepClone } from '@jsopen/objects';
34+
35+
class MyClass {
36+
value = 1;
37+
}
38+
39+
const original = { obj: new MyClass() };
40+
const copy = deepClone(original);
41+
42+
copy.obj.value = 2;
43+
console.log(original.obj.value); // 1 (instance was cloned)
44+
```
45+
46+
## Options
47+
48+
Both functions accept the same options as the [`merge`](merge.md) function (except `deepClone` which forces `deep: 'full'`).
49+
50+
| Option | Type | Default | Description |
51+
|:------------------|:------------------------------------|:--------|:--------------------------------------------------------------------------------------|
52+
| `deep` | `boolean \| 'full' \| CallbackFn` | `true`* | Enables deep cloning. (`true` for `clone`, `'full'` for `deepClone`) |
53+
| `copyDescriptors` | `boolean` | `false` | If true, copies property descriptors (getters, setters, etc.). |
54+
| `symbolKeys` | `boolean` | `true` | Whether to include properties with Symbol keys. |
55+
| `ignoreUndefined` | `boolean` | `true` | If true, `undefined` values will be ignored. |
56+
| `ignoreNulls` | `boolean` | `false` | If true, `null` values will be ignored. |
57+
| `filter` | `CallbackFn` | - | Callback to exclude properties from being cloned. |
58+
59+
*\*Note: In `clone`, the default for `deep` is `true`. In `deepClone`, it is fixed to `'full'`.*
60+
61+
## Usage Examples
62+
63+
### Cloning with Filtering
64+
65+
```typescript
66+
const original = { a: 1, secret: '123' };
67+
const copy = clone(original, {
68+
filter: (val, { key }) => key !== 'secret'
69+
});
70+
// copy is { a: 1 }
71+
```
72+
73+
### Preserving Property Descriptors
74+
75+
```typescript
76+
const original = {
77+
get id() { return Math.random(); }
78+
};
79+
const copy = clone(original, { copyDescriptors: true });
80+
// copy now has the 'id' getter.
81+
```

docs/merge.md

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
# merge
2+
3+
The `merge` function is a powerful, flexible tool for merging objects, arrays, and their nested properties.
4+
It supports deep merging, merging multiple sources, property descriptor cloning, array merging strategies,
5+
and custom filtering logic.
6+
7+
## Features
8+
9+
- **Deep Merging**: Option to deeply merge objects and arrays.
10+
- **Multiple Sources**: Merge an array of objects into a target object.
11+
- **Merge Arrays**: Choose between overwriting, appending, or merging unique array elements.
12+
- **Property Descriptors**: Clone getter/setter and other property descriptors.
13+
- **Custom Filters**: Exclude specific properties based on custom logic.
14+
- **Security**: Built-in protection against prototype pollution (`__proto__`, `constructor`).
15+
- **TypeScript Ready**: Full type safety and intelligent merging of source types.
16+
17+
## Basic Usage
18+
19+
```typescript
20+
import { merge } from '@jsopen/objects';
21+
22+
const target = { a: 1, b: 2 };
23+
const source = { b: 3, c: 4 };
24+
25+
merge(target, source);
26+
// target is now: { a: 1, b: 3, c: 4 }
27+
```
28+
29+
### Merging Multiple Sources
30+
31+
```typescript
32+
const target = { a: 1 };
33+
const sources = [{ b: 2 }, { c: 3 }];
34+
35+
merge(target, sources);
36+
// target is now: { a: 1, b: 2, c: 3 }
37+
```
38+
39+
## Options
40+
41+
The `merge` function accepts an optional `Options` object as its third argument.
42+
43+
| Option | Type | Default | Description |
44+
|:------------------|:------------------------------------|:--------|:--------------------------------------------------------------------------------------|
45+
| `deep` | `boolean \| 'full' \| CallbackFn` | `false` | Enables deep merging. `true` only for plain objects/arrays, `'full'` for all objects. |
46+
| `mergeArrays` | `boolean \| 'unique' \| CallbackFn` | `false` | Controls how arrays are merged. |
47+
| `keepExisting` | `boolean \| CallbackFn` | `false` | If true, does not overwrite existing properties in the target. |
48+
| `copyDescriptors` | `boolean` | `false` | If true, copies property descriptors (getters, setters, etc.). |
49+
| `symbolKeys` | `boolean` | `true` | Whether to include properties with Symbol keys. |
50+
| `ignoreUndefined` | `boolean` | `true` | If true, `undefined` values in source will be ignored. |
51+
| `ignoreNulls` | `boolean` | `false` | If true, `null` values in source will be ignored. |
52+
| `ignoreSource` | `CallbackFn` | - | Callback to ignore specific source values. |
53+
| `filter` | `CallbackFn` | - | Callback to exclude properties from both target and source. |
54+
55+
### Deep Merging
56+
57+
- `true`: Merges plain objects and arrays recursively. Non-plain objects (class instances) are assigned by reference.
58+
- `'full'`: Merges all objects including class instances (excluding built-in types like `Date`, `RegExp`, etc.).
59+
- `CallbackFn`: A function `(value, context) => boolean` to decide whether to go deep for a specific path.
60+
61+
```typescript
62+
merge(target, source, { deep: true });
63+
64+
// Using a callback to deep merge only specific paths
65+
merge(target, source, {
66+
deep: (val, { path }) => path.startsWith('metadata')
67+
});
68+
```
69+
70+
### Array Merging
71+
72+
- `true`: Appends source array elements to the target array.
73+
- `'unique'`: Appends source array elements and ensures the resulting array has unique values.
74+
- `CallbackFn`: A function `(value, context) => boolean` to decide whether to merge arrays for a specific path.
75+
76+
```typescript
77+
const target = { tags: ['js'] };
78+
const source = { tags: ['ts', 'js'] };
79+
80+
merge(target, source, { mergeArrays: 'unique' });
81+
// target.tags is now: ['js', 'ts']
82+
```
83+
84+
### Property Descriptors
85+
86+
By default, `merge` copies values. Enable `copyDescriptors` to preserve getters, setters, and other attributes.
87+
88+
```typescript
89+
const source = {
90+
get id() {
91+
return Math.random();
92+
}
93+
};
94+
95+
merge(target, source, { copyDescriptors: true });
96+
// target now has the 'id' getter, not just a static value.
97+
```
98+
99+
### Filtering and Ignoring
100+
101+
```typescript
102+
merge(target, source, {
103+
// Ignore source values that are booleans
104+
ignoreSource: (val) => typeof val === 'boolean',
105+
106+
// Ignore specific keys
107+
filter: (val, { key }) => key !== 'internalSecret',
108+
109+
// Keep target's value if it already exists
110+
keepExisting: true
111+
});
112+
```
113+
114+
## Security
115+
116+
The function automatically skips keys that could lead to prototype pollution:
117+
118+
- `__proto__`
119+
- `constructor`
120+
121+
## Type Definitions
122+
123+
```typescript
124+
type CallbackFn = (value: any, ctx: CallbackContext) => boolean;
125+
126+
interface CallbackContext {
127+
source: any;
128+
target: any;
129+
key: string | symbol | number;
130+
path: string; // Dot-separated path, e.g., "user.profile.id"
131+
}
132+
133+
interface Options {
134+
deep?: boolean | 'full' | CallbackFn;
135+
symbolKeys?: boolean;
136+
mergeArrays?: boolean | 'unique' | CallbackFn;
137+
keepExisting?: boolean | CallbackFn;
138+
copyDescriptors?: boolean;
139+
ignoreSource?: CallbackFn;
140+
ignoreUndefined?: boolean;
141+
ignoreNulls?: boolean;
142+
filter?: CallbackFn;
143+
}
144+
```

docs/omit.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# omit / omitUndefined / omitNull / omitNullish
2+
3+
These functions provide convenient ways to exclude specific properties, `undefined` values, or `null` values from an object. They are built on top of the [`merge`](merge.md) function.
4+
5+
## Functions
6+
7+
### `omit(obj, keys)`
8+
9+
Creates a new object that includes all properties of `obj` except the specified `keys`. This is a shallow operation.
10+
11+
```typescript
12+
import { omit } from '@jsopen/objects';
13+
14+
const original = { a: 1, b: 2, c: 3 };
15+
const result = omit(original, ['b', 'c']);
16+
// result is { a: 1 }
17+
```
18+
19+
### `omitUndefined(obj, deep?)`
20+
21+
Creates a new object by excluding all properties that have `undefined` as their value.
22+
23+
- `deep: false` (default): Only top-level `undefined` values are removed.
24+
- `deep: true`: Recursively removes `undefined` values from plain objects and arrays.
25+
- `deep: 'full'`: Recursively removes `undefined` values from all objects including class instances.
26+
27+
```typescript
28+
import { omitUndefined } from '@jsopen/objects';
29+
30+
const original = { a: 1, b: undefined, c: { d: undefined } };
31+
const result = omitUndefined(original, true);
32+
// result is { a: 1, c: {} }
33+
```
34+
35+
### `omitNull(obj, deep?)`
36+
37+
Creates a new object by excluding all properties that have `null` as their value.
38+
39+
- `deep: false` (default): Only top-level `null` values are removed.
40+
- `deep: true`: Recursively removes `null` values from plain objects and arrays.
41+
- `deep: 'full'`: Recursively removes `null` values from all objects including class instances.
42+
43+
```typescript
44+
import { omitNull } from '@jsopen/objects';
45+
46+
const original = { a: 1, b: null, c: { d: null } };
47+
const result = omitNull(original, true);
48+
// result is { a: 1, c: {} }
49+
```
50+
51+
### `omitNullish(obj, deep?)`
52+
53+
Creates a new object by excluding all properties that have either `null` or `undefined` as their value.
54+
55+
- `deep: false` (default): Only top-level nullish values are removed.
56+
- `deep: true`: Recursively removes nullish values from plain objects and arrays.
57+
- `deep: 'full'`: Recursively removes nullish values from all objects including class instances.
58+
59+
```typescript
60+
import { omitNullish } from '@jsopen/objects';
61+
62+
const original = { a: 1, b: null, c: undefined, d: { e: null } };
63+
const result = omitNullish(original, true);
64+
// result is { a: 1, d: {} }
65+
```
66+
67+
## Options
68+
69+
These functions use standard [`merge`](merge.md) options internally. `omitUndefined`, `omitNull`, and `omitNullish` preserve property descriptors by default (`copyDescriptors: true`).

docs/update-error-message.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# updateErrorMessage
2+
3+
The `updateErrorMessage` function updates an `Error` object's message and refreshes its stack trace to reflect the point where the update occurred.
4+
5+
## Function
6+
7+
### `updateErrorMessage(err, newMessage)`
8+
9+
Updates the message of the error and captures a new stack trace.
10+
11+
- **V8 Engines (Node.js, Chrome)**: Uses `Error.captureStackTrace` for optimal performance and accuracy.
12+
- **Other Engines**: Provides a fallback mechanism that manually reconstructs the stack trace with the new message while preserving original stack frames.
13+
14+
```typescript
15+
import { updateErrorMessage } from '@jsopen/objects';
16+
17+
const err = new Error('Original message');
18+
updateErrorMessage(err, 'Updated message');
19+
20+
console.log(err.message); // "Updated message"
21+
console.log(err.stack); // Stack trace starting with "Error: Updated message"
22+
```
23+
24+
## Why use this?
25+
26+
When re-throwing or wrapping errors, simply changing `err.message` might not update the first line of `err.stack` in some environments, or you might want the stack trace to point to the location where the error was "enhanced" rather than where it was originally created. `updateErrorMessage` ensures consistency across different JavaScript engines.

0 commit comments

Comments
 (0)