ts-data-forge 14.10.0 → 14.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/dist/array/impl/array-utils-element-access.d.mts.map +1 -1
- package/dist/array/impl/array-utils-element-access.mjs +4 -4
- package/dist/array/impl/array-utils-element-access.mjs.map +1 -1
- package/dist/array/impl/array-utils-length-bounded-array-cast.d.mts +8 -8
- package/dist/array/impl/array-utils-length-bounded-array-cast.d.mts.map +1 -1
- package/dist/array/impl/array-utils-length-bounded-array-cast.mjs +28 -28
- package/dist/array/impl/array-utils-length-bounded-array-cast.mjs.map +1 -1
- package/dist/array/impl/array-utils-length-bounded-array-guard.d.mts.map +1 -1
- package/dist/array/impl/array-utils-length-bounded-array-guard.mjs +4 -4
- package/dist/array/impl/array-utils-length-bounded-array-guard.mjs.map +1 -1
- package/dist/array/impl/array-utils-length-bounded-tuple-cast.d.mts +3 -4
- package/dist/array/impl/array-utils-length-bounded-tuple-cast.d.mts.map +1 -1
- package/dist/array/impl/array-utils-length-bounded-tuple-cast.mjs +28 -29
- package/dist/array/impl/array-utils-length-bounded-tuple-cast.mjs.map +1 -1
- package/dist/array/impl/array-utils-length-bounded-tuple-guard.d.mts.map +1 -1
- package/dist/array/impl/array-utils-length-bounded-tuple-guard.mjs +3 -3
- package/dist/array/impl/array-utils-length-bounded-tuple-guard.mjs.map +1 -1
- package/dist/array/impl/array-utils-modification.d.mts +1 -1
- package/dist/array/impl/array-utils-modification.d.mts.map +1 -1
- package/dist/array/impl/array-utils-modification.mjs +15 -15
- package/dist/array/impl/array-utils-modification.mjs.map +1 -1
- package/dist/array/impl/array-utils-reducing-value.d.mts.map +1 -1
- package/dist/array/impl/array-utils-reducing-value.mjs +12 -12
- package/dist/array/impl/array-utils-reducing-value.mjs.map +1 -1
- package/dist/array/impl/array-utils-search.d.mts.map +1 -1
- package/dist/array/impl/array-utils-search.mjs +56 -56
- package/dist/array/impl/array-utils-search.mjs.map +1 -1
- package/dist/array/impl/array-utils-slicing.d.mts.map +1 -1
- package/dist/array/impl/array-utils-slicing.mjs +3 -3
- package/dist/array/impl/array-utils-slicing.mjs.map +1 -1
- package/dist/array/impl/array-utils-transformation.d.mts +25 -25
- package/dist/array/impl/array-utils-transformation.d.mts.map +1 -1
- package/dist/array/impl/array-utils-transformation.mjs +22 -22
- package/dist/array/impl/array-utils-transformation.mjs.map +1 -1
- package/dist/array/impl/array-utils-validation.d.mts.map +1 -1
- package/dist/array/impl/array-utils-validation.mjs +2 -2
- package/dist/array/impl/array-utils-validation.mjs.map +1 -1
- package/dist/collections/imap-mapped.d.mts +2 -2
- package/dist/collections/imap-mapped.mjs +6 -3
- package/dist/collections/imap-mapped.mjs.map +1 -1
- package/dist/collections/imap.d.mts +3 -4
- package/dist/collections/imap.d.mts.map +1 -1
- package/dist/collections/imap.mjs +6 -4
- package/dist/collections/imap.mjs.map +1 -1
- package/dist/collections/iset-mapped.d.mts +1 -2
- package/dist/collections/iset-mapped.d.mts.map +1 -1
- package/dist/collections/iset-mapped.mjs.map +1 -1
- package/dist/collections/iset.d.mts +4 -5
- package/dist/collections/iset.d.mts.map +1 -1
- package/dist/collections/iset.mjs +0 -1
- package/dist/collections/iset.mjs.map +1 -1
- package/dist/json/json.d.mts.map +1 -1
- package/dist/json/json.mjs +13 -47
- package/dist/json/json.mjs.map +1 -1
- package/dist/number/num.d.mts +12 -8
- package/dist/number/num.d.mts.map +1 -1
- package/dist/number/num.mjs +5 -1
- package/dist/number/num.mjs.map +1 -1
- package/dist/object/object.d.mts +10 -2
- package/dist/object/object.d.mts.map +1 -1
- package/dist/object/object.mjs +16 -4
- package/dist/object/object.mjs.map +1 -1
- package/dist/others/fast-deep-equal.d.mts.map +1 -1
- package/dist/others/fast-deep-equal.mjs +10 -7
- package/dist/others/fast-deep-equal.mjs.map +1 -1
- package/dist/others/replace-equal-deep.d.mts +9 -9
- package/dist/others/replace-equal-deep.mjs +22 -15
- package/dist/others/replace-equal-deep.mjs.map +1 -1
- package/dist/string/str.d.mts +2 -2
- package/package.json +12 -12
- package/src/array/impl/array-utils-element-access.mts +11 -11
- package/src/array/impl/array-utils-length-bounded-array-cast.mts +69 -69
- package/src/array/impl/array-utils-length-bounded-array-guard.mts +21 -21
- package/src/array/impl/array-utils-length-bounded-array-guard.test.mts +2 -2
- package/src/array/impl/array-utils-length-bounded-tuple-cast.mts +64 -65
- package/src/array/impl/array-utils-length-bounded-tuple-guard.mts +15 -15
- package/src/array/impl/array-utils-modification.mts +54 -54
- package/src/array/impl/array-utils-reducing-value.mts +55 -55
- package/src/array/impl/array-utils-search.mts +114 -114
- package/src/array/impl/array-utils-slicing.mts +16 -16
- package/src/array/impl/array-utils-transformation.mts +96 -96
- package/src/array/impl/array-utils-transformation.test.mts +54 -5
- package/src/array/impl/array-utils-validation.mts +19 -19
- package/src/array/impl/array-utils-validation.test.mts +1 -1
- package/src/collections/imap-mapped.mts +8 -6
- package/src/collections/imap-mapped.test.mts +27 -0
- package/src/collections/imap.mts +9 -8
- package/src/collections/imap.test.mts +14 -1
- package/src/collections/iset-mapped.mts +1 -2
- package/src/collections/iset.mts +4 -5
- package/src/collections/iset.test.mts +1 -1
- package/src/json/json.mts +17 -57
- package/src/json/json.test.mts +32 -0
- package/src/number/num.mts +21 -17
- package/src/number/num.test.mts +13 -1
- package/src/object/object.mts +34 -6
- package/src/object/object.test.mts +55 -0
- package/src/others/fast-deep-equal.mts +17 -10
- package/src/others/fast-deep-equal.test.mts +141 -29
- package/src/others/replace-equal-deep.mts +48 -41
- package/src/others/replace-equal-deep.test.mts +39 -7
- package/src/string/str.mts +2 -2
package/src/json/json.mts
CHANGED
|
@@ -3,8 +3,7 @@ import type {
|
|
|
3
3
|
UintRangeInclusive,
|
|
4
4
|
UnknownRecord,
|
|
5
5
|
} from 'ts-type-forge';
|
|
6
|
-
import {
|
|
7
|
-
import { pipe, Result } from '../functional/index.mjs';
|
|
6
|
+
import { Result } from '../functional/index.mjs';
|
|
8
7
|
import { isRecord } from '../guard/index.mjs';
|
|
9
8
|
import { castMutable, unknownToString } from '../others/index.mjs';
|
|
10
9
|
|
|
@@ -323,63 +322,24 @@ export namespace Json {
|
|
|
323
322
|
value: UnknownRecord,
|
|
324
323
|
space?: UintRangeInclusive<1, 10> | string,
|
|
325
324
|
): Result<string | undefined, string> => {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
325
|
+
// The keys are collected by a first pass with a function replacer, which
|
|
326
|
+
// `JSON.stringify` calls at every nesting level with the value after
|
|
327
|
+
// `toJSON`, so the allowlist of the second pass misses none of them. The
|
|
328
|
+
// allowlist also fixes the order, sorting integer-like keys as strings.
|
|
329
|
+
const mut_keys = new Set<string>();
|
|
329
330
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
/**
|
|
335
|
-
* @param obj - The record to extract keys from. Must be a plain object.
|
|
336
|
-
* @param mut_keys - A mutable array to accumulate the collected keys. This
|
|
337
|
-
* array will be modified in-place by the function for performance reasons.
|
|
338
|
-
* @internal
|
|
339
|
-
* Recursively collects all property keys from a nested object structure.
|
|
340
|
-
*
|
|
341
|
-
* This helper function traverses an object and its nested objects and arrays,
|
|
342
|
-
* collecting all string keys found at any level of nesting. The function mutates
|
|
343
|
-
* the provided keys array for performance reasons.
|
|
344
|
-
*/
|
|
345
|
-
const keysDeepImpl = (
|
|
346
|
-
obj: UnknownRecord,
|
|
347
|
-
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
348
|
-
mut_keys: string[],
|
|
349
|
-
): void => {
|
|
350
|
-
for (const [k, v] of Object.entries(obj)) {
|
|
351
|
-
mut_keys.push(k);
|
|
352
|
-
|
|
353
|
-
if (isRecord(v)) {
|
|
354
|
-
keysDeepImpl(v, mut_keys);
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
if (Array.isArray(v)) {
|
|
358
|
-
for (const li of v) {
|
|
359
|
-
if (isRecord(li)) {
|
|
360
|
-
keysDeepImpl(li, mut_keys);
|
|
331
|
+
const collected = stringify(value, (_key, v) => {
|
|
332
|
+
if (isRecord(v)) {
|
|
333
|
+
for (const k of Object.keys(v)) {
|
|
334
|
+
mut_keys.add(k);
|
|
361
335
|
}
|
|
362
336
|
}
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
};
|
|
366
337
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
* @returns A readonly array of all string keys found in the object and its
|
|
370
|
-
* nested objects/arrays. May contain duplicates if the same key appears at
|
|
371
|
-
* multiple levels.
|
|
372
|
-
* @internal
|
|
373
|
-
* Extracts all property keys from a nested object structure into a flat array.
|
|
374
|
-
*
|
|
375
|
-
* This function serves as a safe wrapper around `keysDeepImpl`, creating a new
|
|
376
|
-
* mutable array and passing it to the recursive implementation. The result
|
|
377
|
-
* contains all keys found at any level of nesting within the input object.
|
|
378
|
-
*/
|
|
379
|
-
const keysDeep = (obj: UnknownRecord): readonly string[] => {
|
|
380
|
-
const mut_keys: string[] = [];
|
|
338
|
+
return v;
|
|
339
|
+
});
|
|
381
340
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
};
|
|
341
|
+
return Result.isErr(collected)
|
|
342
|
+
? collected
|
|
343
|
+
: stringifySelected(value, Array.from(mut_keys).toSorted(), space);
|
|
344
|
+
};
|
|
345
|
+
}
|
package/src/json/json.test.mts
CHANGED
|
@@ -709,6 +709,38 @@ describe('stringifySortedKey', () => {
|
|
|
709
709
|
}
|
|
710
710
|
});
|
|
711
711
|
|
|
712
|
+
test('keeps the properties of an object nested in an array of arrays', () => {
|
|
713
|
+
const result = Json.stringifySortedKey({ a: [[{ c: 1, b: 2 }]] });
|
|
714
|
+
|
|
715
|
+
assert.isTrue(Result.isOk(result));
|
|
716
|
+
|
|
717
|
+
expect(result.value).toBe('{"a":[[{"b":2,"c":1}]]}');
|
|
718
|
+
|
|
719
|
+
const other = Json.stringifySortedKey({ a: [[{ c: 1, b: 3 }]] });
|
|
720
|
+
|
|
721
|
+
assert.isTrue(Result.isOk(other));
|
|
722
|
+
|
|
723
|
+
expect(other.value).not.toBe(result.value);
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
test('keeps the properties of what toJSON returns', () => {
|
|
727
|
+
const result = Json.stringifySortedKey({
|
|
728
|
+
x: { toJSON: () => ({ z: 1, y: 2 }) },
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
assert.isTrue(Result.isOk(result));
|
|
732
|
+
|
|
733
|
+
expect(result.value).toBe('{"x":{"y":2,"z":1}}');
|
|
734
|
+
});
|
|
735
|
+
|
|
736
|
+
test('sorts integer-like keys as strings', () => {
|
|
737
|
+
const result = Json.stringifySortedKey({ 2: 'b', 10: 'a', x: 'c' });
|
|
738
|
+
|
|
739
|
+
assert.isTrue(Result.isOk(result));
|
|
740
|
+
|
|
741
|
+
expect(result.value).toBe('{"10":"a","2":"b","x":"c"}');
|
|
742
|
+
});
|
|
743
|
+
|
|
712
744
|
test('should handle empty object', () => {
|
|
713
745
|
const result = Json.stringifySortedKey({});
|
|
714
746
|
|
package/src/number/num.mts
CHANGED
|
@@ -120,8 +120,8 @@ export namespace Num {
|
|
|
120
120
|
*
|
|
121
121
|
* assert.strictEqual(Result.unwrapOk(Num.safeParseInt(' ')), undefined);
|
|
122
122
|
*
|
|
123
|
-
* // A value that overflows to Infinity is not an integer either, even
|
|
124
|
-
* //
|
|
123
|
+
* // A value that overflows to Infinity is not an integer either, even though
|
|
124
|
+
* // native `parseInt` reads a 1 out of it.
|
|
125
125
|
*
|
|
126
126
|
* assert.strictEqual(Number.parseInt('1e400', 10), 1);
|
|
127
127
|
*
|
|
@@ -135,10 +135,10 @@ export namespace Num {
|
|
|
135
135
|
*/
|
|
136
136
|
export const safeParseInt = (s: string): Result<Int, Error> =>
|
|
137
137
|
// The parsing itself is `SafeNumber.parseInteger` (Sumi D-49 (c)): one
|
|
138
|
-
// implementation, in the package that owns the standard-library
|
|
139
|
-
//
|
|
140
|
-
//
|
|
141
|
-
//
|
|
138
|
+
// implementation, in the package that owns the standard-library boundary.
|
|
139
|
+
// What stays here is this function's own contract — the `Int` brand on the
|
|
140
|
+
// success side, and an `Error` rather than a tagged record on the failure
|
|
141
|
+
// side.
|
|
142
142
|
Result.match(SafeNumber.parseInteger(s), {
|
|
143
143
|
// eslint-disable-next-line total-functions/no-unsafe-type-assertion
|
|
144
144
|
ok: (n) => Result.ok(n as Int),
|
|
@@ -213,10 +213,10 @@ export namespace Num {
|
|
|
213
213
|
*/
|
|
214
214
|
export const safeParseFloat = (s: string): Result<FiniteNumber, Error> =>
|
|
215
215
|
// The parsing itself is `SafeNumber.parse` (Sumi D-49 (c)): one
|
|
216
|
-
// implementation, in the package that owns the standard-library
|
|
217
|
-
//
|
|
218
|
-
//
|
|
219
|
-
//
|
|
216
|
+
// implementation, in the package that owns the standard-library boundary.
|
|
217
|
+
// What stays here is this function's own contract — the `FiniteNumber`
|
|
218
|
+
// brand on the success side, and an `Error` rather than a tagged record on
|
|
219
|
+
// the failure side.
|
|
220
220
|
Result.match(SafeNumber.parse(s), {
|
|
221
221
|
// eslint-disable-next-line total-functions/no-unsafe-type-assertion
|
|
222
222
|
ok: (n) => Result.ok(n as FiniteNumber),
|
|
@@ -603,15 +603,19 @@ export namespace Num {
|
|
|
603
603
|
/**
|
|
604
604
|
* Performs integer division using floor division.
|
|
605
605
|
*
|
|
606
|
-
* Computes
|
|
607
|
-
*
|
|
606
|
+
* Computes `⌊⌊a⌋ / b⌋`: the dividend is floored, the divisor is used as is,
|
|
607
|
+
* and the quotient is floored. For a positive integer divisor this is
|
|
608
|
+
* `⌊a / b⌋`. The divisor is not floored, since a divisor in `(-1, 1)` would
|
|
609
|
+
* floor to `0` or `-1`.
|
|
608
610
|
*
|
|
609
|
-
*
|
|
610
|
-
*
|
|
611
|
+
* Like `div`, the divisor must be typed as `NonZeroNumber` or a non-zero
|
|
612
|
+
* numeric literal. A divisor of magnitude below `1` makes the quotient larger
|
|
613
|
+
* than the dividend, so it overflows to `±Infinity` once `|a / b|` exceeds
|
|
614
|
+
* `Number.MAX_VALUE` (e.g. `1e300 / 1e-10`), as `mul` can.
|
|
611
615
|
*
|
|
612
616
|
* @param a - The dividend
|
|
613
|
-
* @param b - The divisor
|
|
614
|
-
* @returns The integer quotient
|
|
617
|
+
* @param b - The divisor (must be non-zero, enforced by types)
|
|
618
|
+
* @returns The integer quotient `⌊⌊a⌋ / b⌋`
|
|
615
619
|
*/
|
|
616
620
|
export const divInt = <
|
|
617
621
|
A extends number,
|
|
@@ -621,7 +625,7 @@ export namespace Num {
|
|
|
621
625
|
b: B,
|
|
622
626
|
): DivIntResult<A, B> => {
|
|
623
627
|
// eslint-disable-next-line total-functions/no-partial-division
|
|
624
|
-
const quotient = Math.floor(Math.floor(a) /
|
|
628
|
+
const quotient = Math.floor(Math.floor(a) / b);
|
|
625
629
|
|
|
626
630
|
// eslint-disable-next-line total-functions/no-unsafe-type-assertion
|
|
627
631
|
return quotient as DivIntResult<A, B>;
|
package/src/number/num.test.mts
CHANGED
|
@@ -354,7 +354,19 @@ describe('Num test', () => {
|
|
|
354
354
|
|
|
355
355
|
expect(Num.divInt(-7, 2)).toBe(-4); // floor division
|
|
356
356
|
|
|
357
|
-
expect(Num.divInt(10.7, asNonZeroFiniteNumber(3.2))).toBe(3); //
|
|
357
|
+
expect(Num.divInt(10.7, asNonZeroFiniteNumber(3.2))).toBe(3); // ⌊⌊10.7⌋ / 3.2⌋
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
test('a divisor in (-1, 1) is not floored to 0 or -1', () => {
|
|
361
|
+
expect(Num.divInt(5, asNonZeroFiniteNumber(0.5))).toBe(10);
|
|
362
|
+
|
|
363
|
+
expect(Num.divInt(0.3, asNonZeroFiniteNumber(0.5))).toBe(0);
|
|
364
|
+
|
|
365
|
+
expect(Num.divInt(-5, asNonZeroFiniteNumber(0.5))).toBe(-10);
|
|
366
|
+
|
|
367
|
+
expect(Num.divInt(5, asNonZeroFiniteNumber(-0.5))).toBe(-10);
|
|
368
|
+
|
|
369
|
+
expect(Num.divInt(7, asNonZeroFiniteNumber(-2.5))).toBe(-3);
|
|
358
370
|
});
|
|
359
371
|
});
|
|
360
372
|
|
package/src/object/object.mts
CHANGED
|
@@ -91,6 +91,10 @@ export namespace Obj {
|
|
|
91
91
|
* **Type Safety**: Only keys that exist in the source record type are
|
|
92
92
|
* allowed, preventing runtime errors from accessing non-existent properties.
|
|
93
93
|
*
|
|
94
|
+
* **Key handling**: a numeric key `1` matches the property `'1'`, and
|
|
95
|
+
* symbol-keyed properties are picked like string-keyed ones. Only own
|
|
96
|
+
* enumerable properties are read.
|
|
97
|
+
*
|
|
94
98
|
* @example
|
|
95
99
|
*
|
|
96
100
|
* ```ts
|
|
@@ -150,12 +154,12 @@ export namespace Obj {
|
|
|
150
154
|
case 2: {
|
|
151
155
|
const [record, keys] = args;
|
|
152
156
|
|
|
153
|
-
const keysSet =
|
|
157
|
+
const keysSet = toOwnKeySet(keys);
|
|
154
158
|
|
|
155
159
|
return (
|
|
156
160
|
// eslint-disable-next-line total-functions/no-unsafe-type-assertion
|
|
157
161
|
Object.fromEntries(
|
|
158
|
-
|
|
162
|
+
ownEnumerableEntries(record).filter(([k]) => keysSet.has(k)),
|
|
159
163
|
) as never
|
|
160
164
|
);
|
|
161
165
|
}
|
|
@@ -177,6 +181,10 @@ export namespace Obj {
|
|
|
177
181
|
* allowed, and the return type precisely reflects which properties remain
|
|
178
182
|
* after omission.
|
|
179
183
|
*
|
|
184
|
+
* **Key handling**: a numeric key `1` matches the property `'1'`, and
|
|
185
|
+
* symbol-keyed properties are kept unless omitted, like string-keyed ones.
|
|
186
|
+
* Only own enumerable properties are copied.
|
|
187
|
+
*
|
|
180
188
|
* @example
|
|
181
189
|
*
|
|
182
190
|
* ```ts
|
|
@@ -238,12 +246,12 @@ export namespace Obj {
|
|
|
238
246
|
case 2: {
|
|
239
247
|
const [record, keys] = args;
|
|
240
248
|
|
|
241
|
-
const keysSet =
|
|
249
|
+
const keysSet = toOwnKeySet(keys);
|
|
242
250
|
|
|
243
251
|
return (
|
|
244
252
|
// eslint-disable-next-line total-functions/no-unsafe-type-assertion
|
|
245
253
|
Object.fromEntries(
|
|
246
|
-
|
|
254
|
+
ownEnumerableEntries(record).filter(([k]) => !keysSet.has(k)),
|
|
247
255
|
) as never
|
|
248
256
|
);
|
|
249
257
|
}
|
|
@@ -337,8 +345,8 @@ export namespace Obj {
|
|
|
337
345
|
* This is what `Object.fromEntries(Object.entries(record).map(...))` cannot
|
|
338
346
|
* express: an entries array carries no evidence that it still covers every
|
|
339
347
|
* key of the record it came from, so its element type is all the return type
|
|
340
|
-
* can be built from. Going through a mapped type instead states the
|
|
341
|
-
*
|
|
348
|
+
* can be built from. Going through a mapped type instead states the invariant
|
|
349
|
+
* that a value-only transform preserves the key set.
|
|
342
350
|
*
|
|
343
351
|
* **Key handling**: keys reach the callback the way `Object.entries` yields
|
|
344
352
|
* them, so a numeric key `1` arrives as `'1'`. Symbol-keyed properties are
|
|
@@ -996,3 +1004,23 @@ declare namespace TsDataForgeInternals {
|
|
|
996
1004
|
? MergeAll<Rest, MergeTwo<Acc, First>>
|
|
997
1005
|
: Acc;
|
|
998
1006
|
}
|
|
1007
|
+
|
|
1008
|
+
/**
|
|
1009
|
+
* The keys as `Reflect.ownKeys` reports them: a numeric key `1` becomes `'1'`,
|
|
1010
|
+
* a symbol stays a symbol.
|
|
1011
|
+
*/
|
|
1012
|
+
const toOwnKeySet = (
|
|
1013
|
+
keys: readonly PropertyKey[],
|
|
1014
|
+
): ReadonlySet<string | symbol> =>
|
|
1015
|
+
new Set(keys.map((k) => (typeof k === 'number' ? k.toString() : k)));
|
|
1016
|
+
|
|
1017
|
+
/**
|
|
1018
|
+
* The own enumerable entries of `record`, string and symbol keys alike, in
|
|
1019
|
+
* `Reflect.ownKeys` order. `Object.entries` would leave out the symbols.
|
|
1020
|
+
*/
|
|
1021
|
+
const ownEnumerableEntries = (
|
|
1022
|
+
record: UnknownRecord,
|
|
1023
|
+
): readonly (readonly [string | symbol, unknown])[] =>
|
|
1024
|
+
Reflect.ownKeys(record)
|
|
1025
|
+
.filter((k) => Object.prototype.propertyIsEnumerable.call(record, k))
|
|
1026
|
+
.map((k) => [k, Reflect.get(record, k)] as const);
|
|
@@ -89,6 +89,32 @@ describe('pick', () => {
|
|
|
89
89
|
|
|
90
90
|
assert.deepStrictEqual(result, { name: 'Alice' });
|
|
91
91
|
});
|
|
92
|
+
|
|
93
|
+
test('pick matches a numeric key', () => {
|
|
94
|
+
const record = { 1: 'a', b: 2 } as const;
|
|
95
|
+
|
|
96
|
+
const result = Obj.pick(record, [1]);
|
|
97
|
+
|
|
98
|
+
expectType<typeof result, Readonly<{ 1: 'a' }>>('=');
|
|
99
|
+
|
|
100
|
+
assert.deepStrictEqual(result, { 1: 'a' });
|
|
101
|
+
|
|
102
|
+
assert.deepStrictEqual(Obj.pick([1])(record), { 1: 'a' });
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test('pick matches a symbol key', () => {
|
|
106
|
+
const sym = Symbol('s');
|
|
107
|
+
|
|
108
|
+
const record = { [sym]: 'a', b: 2 } as const;
|
|
109
|
+
|
|
110
|
+
const result = Obj.pick(record, [sym]);
|
|
111
|
+
|
|
112
|
+
assert.deepStrictEqual(result, { [sym]: 'a' });
|
|
113
|
+
|
|
114
|
+
assert.deepStrictEqual(Obj.pick([sym])(record), { [sym]: 'a' });
|
|
115
|
+
|
|
116
|
+
assert.deepStrictEqual(Obj.pick(record, ['b']), { b: 2 });
|
|
117
|
+
});
|
|
92
118
|
});
|
|
93
119
|
|
|
94
120
|
describe('omit', () => {
|
|
@@ -132,6 +158,35 @@ describe('omit', () => {
|
|
|
132
158
|
assert.deepStrictEqual(result, original);
|
|
133
159
|
});
|
|
134
160
|
|
|
161
|
+
test('omit matches a numeric key', () => {
|
|
162
|
+
const record = { 1: 'a', b: 2 } as const;
|
|
163
|
+
|
|
164
|
+
const result = Obj.omit(record, [1]);
|
|
165
|
+
|
|
166
|
+
expectType<typeof result, Readonly<{ b: 2 }>>('=');
|
|
167
|
+
|
|
168
|
+
assert.deepStrictEqual(result, { b: 2 });
|
|
169
|
+
|
|
170
|
+
assert.deepStrictEqual(Obj.omit([1])(record), { b: 2 });
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test('omit keeps the symbol keys it is not asked to omit', () => {
|
|
174
|
+
const sym = Symbol('s');
|
|
175
|
+
|
|
176
|
+
const other = Symbol('other');
|
|
177
|
+
|
|
178
|
+
const record = { [sym]: 'a', [other]: 'o', b: 2 } as const;
|
|
179
|
+
|
|
180
|
+
assert.deepStrictEqual(Obj.omit(record, [sym]), { [other]: 'o', b: 2 });
|
|
181
|
+
|
|
182
|
+
assert.deepStrictEqual(Obj.omit([sym])(record), { [other]: 'o', b: 2 });
|
|
183
|
+
|
|
184
|
+
assert.deepStrictEqual(Obj.omit(record, ['b']), {
|
|
185
|
+
[sym]: 'a',
|
|
186
|
+
[other]: 'o',
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
135
190
|
test('should omit multiple keys in curried form', () => {
|
|
136
191
|
const omitBAndD = Obj.omit(['b', 'd']);
|
|
137
192
|
|
|
@@ -67,12 +67,20 @@ export const fastDeepEqual = <T,>(a: T, b: T): boolean => {
|
|
|
67
67
|
return isRegExp(b) && a.source === b.source && a.flags === b.flags;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
// A null-prototype object (`Object.create(null)`, `Object.groupBy`) has
|
|
71
|
+
// neither method, so it falls through to the key comparison.
|
|
72
|
+
if (
|
|
73
|
+
typeof a.valueOf === 'function' &&
|
|
74
|
+
a.valueOf !== Object.prototype.valueOf
|
|
75
|
+
) {
|
|
76
|
+
return typeof b.valueOf === 'function' && a.valueOf() === b.valueOf();
|
|
72
77
|
}
|
|
73
78
|
|
|
74
|
-
if (
|
|
75
|
-
|
|
79
|
+
if (
|
|
80
|
+
typeof a.toString === 'function' &&
|
|
81
|
+
a.toString !== Object.prototype.toString
|
|
82
|
+
) {
|
|
83
|
+
return typeof b.toString === 'function' && a.toString() === b.toString();
|
|
76
84
|
}
|
|
77
85
|
|
|
78
86
|
const aKeys: readonly string[] = Object.keys(a);
|
|
@@ -84,12 +92,11 @@ export const fastDeepEqual = <T,>(a: T, b: T): boolean => {
|
|
|
84
92
|
aKeys.every(
|
|
85
93
|
(key) =>
|
|
86
94
|
hasKey(b, key) &&
|
|
87
|
-
// React-specific:
|
|
88
|
-
//
|
|
89
|
-
// and is not
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
fastDeepEqual(a[key], b[key]),
|
|
95
|
+
// React-specific: a React element (marked by a symbol `$$typeof`)
|
|
96
|
+
// has its _owner skipped, i.e. treated as equal. It holds circular
|
|
97
|
+
// references and is not part of what the element renders.
|
|
98
|
+
((key === '_owner' && typeof a['$$typeof'] === 'symbol') ||
|
|
99
|
+
fastDeepEqual(a[key], b[key])),
|
|
93
100
|
)
|
|
94
101
|
);
|
|
95
102
|
}
|