mirror of
https://github.com/toss/es-toolkit.git
synced 2024-11-28 03:34:26 +03:00
feat(cloneDeep): Support full feature parity with lodash
This commit is contained in:
parent
5c1251c5a5
commit
876931405a
@ -187,7 +187,7 @@ Even if a feature is marked "in review," it might already be under review to ens
|
||||
| ---------------------------------------------------------------------- | --------------------- |
|
||||
| [castArray](https://lodash.com/docs/4.17.15#castArray) | ❌ |
|
||||
| [clone](https://lodash.com/docs/4.17.15#clone) | 📝 |
|
||||
| [cloneDeep](https://lodash.com/docs/4.17.15#cloneDeep) | ❌ |
|
||||
| [cloneDeep](https://lodash.com/docs/4.17.15#cloneDeep) | ✅ |
|
||||
| [cloneDeepWith](https://lodash.com/docs/4.17.15#cloneDeepWith) | ❌ |
|
||||
| [cloneWith](https://lodash.com/docs/4.17.15#cloneWith) | ❌ |
|
||||
| [conformsTo](https://lodash.com/docs/4.17.15#conformsTo) | ❌ |
|
||||
|
@ -188,7 +188,7 @@ chunk([1, 2, 3, 4], 0);
|
||||
| ---------------------------------------------------------------------- | --------- |
|
||||
| [castArray](https://lodash.com/docs/4.17.15#castArray) | ❌ |
|
||||
| [clone](https://lodash.com/docs/4.17.15#clone) | 📝 |
|
||||
| [cloneDeep](https://lodash.com/docs/4.17.15#cloneDeep) | ❌ |
|
||||
| [cloneDeep](https://lodash.com/docs/4.17.15#cloneDeep) | ✅ |
|
||||
| [cloneDeepWith](https://lodash.com/docs/4.17.15#cloneDeepWith) | ❌ |
|
||||
| [cloneWith](https://lodash.com/docs/4.17.15#cloneWith) | ❌ |
|
||||
| [conformsTo](https://lodash.com/docs/4.17.15#conformsTo) | ❌ |
|
||||
|
@ -187,7 +187,7 @@ chunk([1, 2, 3, 4], 0);
|
||||
| ---------------------------------------------------------------------- | -------- |
|
||||
| [castArray](https://lodash.com/docs/4.17.15#castArray) | ❌ |
|
||||
| [clone](https://lodash.com/docs/4.17.15#clone) | 📝 |
|
||||
| [cloneDeep](https://lodash.com/docs/4.17.15#cloneDeep) | ❌ |
|
||||
| [cloneDeep](https://lodash.com/docs/4.17.15#cloneDeep) | ✅ |
|
||||
| [cloneDeepWith](https://lodash.com/docs/4.17.15#cloneDeepWith) | ❌ |
|
||||
| [cloneWith](https://lodash.com/docs/4.17.15#cloneWith) | ❌ |
|
||||
| [conformsTo](https://lodash.com/docs/4.17.15#conformsTo) | ❌ |
|
||||
|
291
src/compat/object/cloneDeep.spec.ts
Normal file
291
src/compat/object/cloneDeep.spec.ts
Normal file
@ -0,0 +1,291 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { cloneDeep } from "./cloneDeep";
|
||||
import { range } from "../../math/range";
|
||||
import { LARGE_ARRAY_SIZE } from "../_internal/LARGE_ARRAY_SIZE";
|
||||
import { args } from "../_internal/args";
|
||||
import { stubTrue } from "../_internal/stubTrue";
|
||||
|
||||
describe('cloneDeep', () => {
|
||||
it('should deep clone objects with circular references', () => {
|
||||
const object: any = {
|
||||
foo: { b: { c: { d: {} } } },
|
||||
bar: {},
|
||||
};
|
||||
|
||||
object.foo.b.c.d = object;
|
||||
object.bar.b = object.foo.b;
|
||||
|
||||
const actual = cloneDeep(object);
|
||||
|
||||
expect(actual.bar.b).toBe(actual.foo.b);
|
||||
expect(actual).toBe(actual.foo.b.c.d);
|
||||
expect(actual).not.toBe(object);
|
||||
});
|
||||
|
||||
it('should deep clone objects with lots of circular references', () => {
|
||||
const cyclical: any = {};
|
||||
|
||||
range(LARGE_ARRAY_SIZE + 1).forEach((index) => {
|
||||
cyclical[`v${index}`] = [index ? cyclical[`v${index - 1}`] : cyclical];
|
||||
});
|
||||
|
||||
const clone = cloneDeep(cyclical);
|
||||
|
||||
const actual = clone[`v${LARGE_ARRAY_SIZE}`][0];
|
||||
|
||||
expect(actual).toBe(clone[`v${LARGE_ARRAY_SIZE - 1}`]);
|
||||
expect(actual).not.toBe(cyclical[`v${LARGE_ARRAY_SIZE - 1}`]);
|
||||
});
|
||||
|
||||
|
||||
class Foo {
|
||||
a = 1;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
Foo.prototype.b = 1;
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
Foo.c = function () { };
|
||||
|
||||
var map = new Map();
|
||||
map.set('a', 1);
|
||||
map.set('b', 2);
|
||||
|
||||
var set = new Set();
|
||||
set.add(1);
|
||||
set.add(2);
|
||||
|
||||
const objects = {
|
||||
booleans: false,
|
||||
'boolean objects': Object(false),
|
||||
'date objects': new Date(),
|
||||
'Foo instances': new Foo(),
|
||||
objects: { a: 0, b: 1, c: 2 },
|
||||
'objects with object values': { a: /a/, b: ['B'], c: { C: 1 } },
|
||||
maps: map,
|
||||
'null values': null,
|
||||
numbers: 0,
|
||||
'number objects': Object(0),
|
||||
regexes: /a/gim,
|
||||
sets: set,
|
||||
strings: 'a',
|
||||
'string objects': Object('a'),
|
||||
'undefined values': undefined,
|
||||
};
|
||||
|
||||
it(`should clone arguments objects`, () => {
|
||||
const actual = cloneDeep(args);
|
||||
|
||||
expect(actual).toEqual(args);
|
||||
expect(actual).not.toBe(args);
|
||||
});
|
||||
|
||||
it(`should clone arrays`, () => {
|
||||
const object = ['a', ''];
|
||||
|
||||
const actual = cloneDeep(['a', '']);
|
||||
|
||||
expect(actual).toEqual(object);
|
||||
expect(actual).not.toBe(object);
|
||||
});
|
||||
|
||||
it(`should clone array-like objects`, () => {
|
||||
const object = { 0: 'a', length: 1 };
|
||||
|
||||
const actual = cloneDeep(object);
|
||||
|
||||
expect(actual).toEqual(object);
|
||||
expect(actual).not.toBe(object);
|
||||
});
|
||||
|
||||
it('should clone booleans', () => {
|
||||
const object = false;
|
||||
|
||||
const actual = cloneDeep(object);
|
||||
|
||||
expect(actual).toEqual(object);
|
||||
expect(actual).toBe(object);
|
||||
});
|
||||
|
||||
it('should clone boolean objects', () => {
|
||||
const object = Object(false);
|
||||
|
||||
const actual = cloneDeep(object);
|
||||
|
||||
expect(actual).toEqual(object);
|
||||
expect(actual).not.toBe(object);
|
||||
});
|
||||
|
||||
it('should clone date objects', () => {
|
||||
const object = new Date();
|
||||
const actual = cloneDeep(object);
|
||||
|
||||
expect(actual).toEqual(object);
|
||||
expect(actual).not.toBe(object);
|
||||
});
|
||||
|
||||
it('should clone Foo instances', () => {
|
||||
const object = new Foo();
|
||||
const actual = cloneDeep(object);
|
||||
|
||||
expect(actual).toEqual(object);
|
||||
expect(actual).not.toBe(object);
|
||||
});
|
||||
|
||||
it('should clone objects', () => {
|
||||
const object = { a: 0, b: 1, c: 2 };
|
||||
|
||||
const actual = cloneDeep(object);
|
||||
|
||||
expect(actual).toEqual(object);
|
||||
expect(actual).not.toBe(object);
|
||||
});
|
||||
|
||||
it('should clone objects with object values', () => {
|
||||
const object = { a: /a/, b: ['B'], c: { C: 1 } };
|
||||
|
||||
const actual = cloneDeep(object);
|
||||
|
||||
expect(actual).toEqual(object);
|
||||
expect(actual).not.toBe(object);
|
||||
});
|
||||
|
||||
it('should clone maps', () => {
|
||||
const object = map;
|
||||
|
||||
const actual = cloneDeep(object);
|
||||
|
||||
expect(actual).toEqual(object);
|
||||
expect(actual).not.toBe(object);
|
||||
});
|
||||
|
||||
it('should clone null values', () => {
|
||||
const object = null;
|
||||
const actual = cloneDeep(object);
|
||||
|
||||
expect(actual).toEqual(object);
|
||||
expect(actual).toBe(object);
|
||||
});
|
||||
|
||||
it('should clone numbers', () => {
|
||||
const object = 0;
|
||||
|
||||
const actual = cloneDeep(object);
|
||||
|
||||
expect(actual).toEqual(object);
|
||||
expect(actual).toBe(object);
|
||||
});
|
||||
|
||||
it('should clone number objects', () => {
|
||||
const object = Object(0);
|
||||
const actual = cloneDeep(object);
|
||||
|
||||
expect(actual).toEqual(object);
|
||||
expect(actual).not.toBe(object);
|
||||
});
|
||||
|
||||
it('should clone regexes', () => {
|
||||
const object = /a/gim;
|
||||
|
||||
const actual = cloneDeep(object);
|
||||
|
||||
expect(actual).toEqual(object);
|
||||
expect(actual).not.toBe(object);
|
||||
});
|
||||
|
||||
it('should clone sets', () => {
|
||||
const object = set;
|
||||
const actual = cloneDeep(object);
|
||||
|
||||
expect(actual).toEqual(object);
|
||||
expect(actual).not.toBe(object);
|
||||
});
|
||||
|
||||
it('should clone strings', () => {
|
||||
const object = 'a';
|
||||
|
||||
const actual = cloneDeep(object);
|
||||
|
||||
expect(actual).toEqual(object);
|
||||
expect(actual).toBe(object);
|
||||
});
|
||||
|
||||
it('should clone string objects', () => {
|
||||
const object = Object('a');
|
||||
|
||||
const actual = cloneDeep(object);
|
||||
|
||||
expect(actual).toEqual(object);
|
||||
expect(actual).not.toBe(object);
|
||||
});
|
||||
|
||||
it('should clone undefined values', () => {
|
||||
const object = undefined;
|
||||
|
||||
const actual = cloneDeep(object);
|
||||
|
||||
expect(actual).toEqual(object);
|
||||
expect(actual).toBe(object);
|
||||
});
|
||||
|
||||
|
||||
it(`should clone array buffers`, () => {
|
||||
const arrayBuffer = new ArrayBuffer(2);
|
||||
const actual = cloneDeep(arrayBuffer);
|
||||
expect(actual.byteLength).toBe(arrayBuffer.byteLength);
|
||||
expect(actual).not.toBe(arrayBuffer);
|
||||
});
|
||||
|
||||
it(`should clone buffers`, () => {
|
||||
const buffer = Buffer.from([1, 2]);
|
||||
const actual = cloneDeep(buffer);
|
||||
|
||||
expect(actual.byteLength).toBe(buffer.byteLength);
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
expect(actual.inspect()).toBe(buffer.inspect());
|
||||
expect(actual).not.toBe(buffer);
|
||||
|
||||
buffer[0] = 2;
|
||||
expect(actual[0]).toBe(2);
|
||||
});
|
||||
|
||||
|
||||
it(`should clone \`index\` and \`input\` array properties`, () => {
|
||||
const array = /c/.exec('abcde');
|
||||
const actual = cloneDeep(array);
|
||||
|
||||
expect(actual?.index).toBe(2);
|
||||
expect(actual?.input).toBe('abcde');
|
||||
});
|
||||
|
||||
|
||||
it(`should clone \`lastIndex\` regexp property`, () => {
|
||||
const regexp = /c/g;
|
||||
regexp.exec('abcde');
|
||||
|
||||
expect(cloneDeep(regexp).lastIndex).toBe(3);
|
||||
});
|
||||
|
||||
|
||||
it(`should clone expando properties`, () => {
|
||||
const values = [false, true, 1, 'a'].map((value) => {
|
||||
const object = Object(value);
|
||||
object.a = 1;
|
||||
return object;
|
||||
});
|
||||
|
||||
const expected = values.map(stubTrue);
|
||||
|
||||
const actual = values.map((value) => {
|
||||
return cloneDeep(value).a === 1
|
||||
});
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
85
src/compat/object/cloneDeep.ts
Normal file
85
src/compat/object/cloneDeep.ts
Normal file
@ -0,0 +1,85 @@
|
||||
import { cloneDeep as cloneDeepToolkit, copyProperties } from '../../object/cloneDeep.ts';
|
||||
|
||||
/**
|
||||
* Creates a deep clone of the given object.
|
||||
*
|
||||
* @template T - The type of the object.
|
||||
* @param {T} obj - The object to clone.
|
||||
* @returns {T} - A deep clone of the given object.
|
||||
*
|
||||
* @example
|
||||
* // Clone a primitive values
|
||||
* const num = 29;
|
||||
* const clonedNum = clone(num);
|
||||
* console.log(clonedNum); // 29
|
||||
* console.log(clonedNum === num) ; // true
|
||||
*
|
||||
* @example
|
||||
* // Clone an array
|
||||
* const arr = [1, 2, 3];
|
||||
* const clonedArr = clone(arr);
|
||||
* console.log(clonedArr); // [1, 2, 3]
|
||||
* console.log(clonedArr === arr); // false
|
||||
*
|
||||
* @example
|
||||
* // Clone an array with nested objects
|
||||
* const arr = [1, { a: 1 }, [1, 2, 3]];
|
||||
* const clonedArr = clone(arr);
|
||||
* arr[1].a = 2;
|
||||
* console.log(arr); // [2, { a: 2 }, [1, 2, 3]]
|
||||
* console.log(clonedArr); // [1, { a: 1 }, [1, 2, 3]]
|
||||
* console.log(clonedArr === arr); // false
|
||||
*
|
||||
* @example
|
||||
* // Clone an object
|
||||
* const obj = { a: 1, b: 'es-toolkit', c: [1, 2, 3] };
|
||||
* const clonedObj = clone(obj);
|
||||
* console.log(clonedObj); // { a: 1, b: 'es-toolkit', c: [1, 2, 3] }
|
||||
* console.log(clonedObj === obj); // false
|
||||
*
|
||||
* @example
|
||||
* // Clone an object with nested objects
|
||||
* const obj = { a: 1, b: { c: 1 } };
|
||||
* const clonedObj = clone(obj);
|
||||
* obj.b.c = 2;
|
||||
* console.log(obj); // { a: 1, b: { c: 2 } }
|
||||
* console.log(clonedObj); // { a: 1, b: { c: 1 } }
|
||||
* console.log(clonedObj === obj); // false
|
||||
*/
|
||||
export function cloneDeep<T>(obj: T): T {
|
||||
if (typeof obj !== 'object') {
|
||||
return cloneDeepToolkit(obj);
|
||||
}
|
||||
|
||||
switch (Object.prototype.toString.call(obj)) {
|
||||
case '[object Number]':
|
||||
case '[object String]':
|
||||
case '[object Boolean]': {
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
const result = new obj.constructor(obj?.valueOf()) as T;
|
||||
copyProperties(result, obj);
|
||||
return result;
|
||||
}
|
||||
|
||||
case '[object Arguments]': {
|
||||
const result = {} as any;
|
||||
|
||||
copyProperties(result, obj);
|
||||
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
result.length = obj.length;
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
result[Symbol.iterator] = obj[Symbol.iterator];
|
||||
|
||||
return result as T;
|
||||
}
|
||||
|
||||
|
||||
default: {
|
||||
return cloneDeepToolkit(obj);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,3 +1,6 @@
|
||||
import { isPrimitive } from "../predicate/isPrimitive.ts";
|
||||
import { isTypedArray } from "../predicate/isTypedArray.ts";
|
||||
|
||||
/**
|
||||
* Creates a deep clone of the given object.
|
||||
*
|
||||
@ -44,223 +47,159 @@
|
||||
* console.log(clonedObj); // { a: 1, b: { c: 1 } }
|
||||
* console.log(clonedObj === obj); // false
|
||||
*/
|
||||
export function cloneDeep<T>(obj: T): Resolved<T> {
|
||||
export function cloneDeep<T>(obj: T): T {
|
||||
return cloneDeepImpl(obj);
|
||||
}
|
||||
|
||||
function cloneDeepImpl<T>(obj: T, stack = new Map<any, any>()): T {
|
||||
if (isPrimitive(obj)) {
|
||||
return obj as Resolved<T>;
|
||||
return obj as T;
|
||||
}
|
||||
|
||||
if (stack.has(obj)) {
|
||||
return stack.get(obj) as T;
|
||||
}
|
||||
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.map(item => cloneDeep(item)) as Resolved<T>;
|
||||
const result: any = new Array(obj.length);
|
||||
stack.set(obj, result);
|
||||
|
||||
for (let i = 0; i < obj.length; i++) {
|
||||
result[i] = cloneDeepImpl(obj[i], stack);
|
||||
}
|
||||
|
||||
// For RegExpArrays
|
||||
if (obj.hasOwnProperty('index')) {
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
result.index = obj.index;
|
||||
}
|
||||
if (obj.hasOwnProperty('input')) {
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
result.input = obj.input;
|
||||
}
|
||||
|
||||
return result as T;
|
||||
}
|
||||
|
||||
if (obj instanceof Date) {
|
||||
return new Date(obj.getTime()) as Resolved<T>;
|
||||
return new Date(obj.getTime()) as T;
|
||||
}
|
||||
|
||||
if (obj instanceof RegExp) {
|
||||
return new RegExp(obj.source, obj.flags) as Resolved<T>;
|
||||
const result = new RegExp(obj.source, obj.flags);
|
||||
|
||||
result.lastIndex = obj.lastIndex;
|
||||
|
||||
return result as T;
|
||||
}
|
||||
|
||||
if (obj instanceof Map) {
|
||||
const result = new Map();
|
||||
stack.set(obj, result);
|
||||
|
||||
for (const [key, value] of obj.entries()) {
|
||||
result.set(key, cloneDeep(value));
|
||||
result.set(key, cloneDeepImpl(value, stack));
|
||||
}
|
||||
return result as Resolved<T>;
|
||||
|
||||
return result as T;
|
||||
}
|
||||
|
||||
if (obj instanceof Set) {
|
||||
const result = new Set();
|
||||
stack.set(obj, result);
|
||||
|
||||
for (const value of obj.values()) {
|
||||
result.add(cloneDeep(value));
|
||||
result.add(cloneDeepImpl(value, stack));
|
||||
}
|
||||
return result as Resolved<T>;
|
||||
|
||||
return result as T;
|
||||
}
|
||||
|
||||
if (typeof Buffer !== 'undefined' && Buffer.isBuffer(obj)) {
|
||||
return obj.subarray() as T;
|
||||
}
|
||||
|
||||
if (isTypedArray(obj)) {
|
||||
const result = new (Object.getPrototypeOf(obj).constructor)(obj.length);
|
||||
stack.set(obj, result);
|
||||
|
||||
for (let i = 0; i < obj.length; i++) {
|
||||
result[i] = cloneDeep(obj[i]);
|
||||
result[i] = cloneDeepImpl(obj[i], stack);
|
||||
}
|
||||
return result as Resolved<T>;
|
||||
|
||||
return result as T;
|
||||
}
|
||||
|
||||
if (obj instanceof ArrayBuffer || (typeof SharedArrayBuffer !== 'undefined' && obj instanceof SharedArrayBuffer)) {
|
||||
return obj.slice(0) as Resolved<T>;
|
||||
return obj.slice(0) as T;
|
||||
}
|
||||
|
||||
if (obj instanceof DataView) {
|
||||
const result = new DataView(obj.buffer.slice(0));
|
||||
cloneDeepHelper(obj, result);
|
||||
return result as Resolved<T>;
|
||||
stack.set(obj, result);
|
||||
|
||||
copyProperties(result, obj, stack);
|
||||
|
||||
return result as T;
|
||||
}
|
||||
|
||||
// For legacy NodeJS support
|
||||
if (typeof File !== 'undefined' && obj instanceof File) {
|
||||
const result = new File([obj], obj.name, { type: obj.type });
|
||||
cloneDeepHelper(obj, result);
|
||||
return result as Resolved<T>;
|
||||
stack.set(obj, result);
|
||||
|
||||
copyProperties(result, obj, stack);
|
||||
|
||||
return result as T;
|
||||
}
|
||||
|
||||
if (obj instanceof Blob) {
|
||||
const result = new Blob([obj], { type: obj.type });
|
||||
cloneDeepHelper(obj, result);
|
||||
return result as Resolved<T>;
|
||||
stack.set(obj, result);
|
||||
|
||||
copyProperties(result, obj, stack);
|
||||
|
||||
return result as T;
|
||||
}
|
||||
|
||||
if (obj instanceof Error) {
|
||||
const result = new (obj.constructor as { new(): Error })();
|
||||
stack.set(obj, result);
|
||||
|
||||
result.message = obj.message;
|
||||
result.name = obj.name;
|
||||
result.stack = obj.stack;
|
||||
result.cause = obj.cause;
|
||||
cloneDeepHelper(obj, result);
|
||||
return result as Resolved<T>;
|
||||
|
||||
copyProperties(result, obj, stack);
|
||||
|
||||
return result as T;
|
||||
}
|
||||
|
||||
if (typeof obj === 'object' && obj !== null) {
|
||||
const result = {};
|
||||
cloneDeepHelper(obj, result);
|
||||
return result as Resolved<T>;
|
||||
stack.set(obj, result);
|
||||
|
||||
copyProperties(result, obj, stack);
|
||||
|
||||
return result as T;
|
||||
}
|
||||
|
||||
return obj as Resolved<T>;
|
||||
}
|
||||
|
||||
type Primitive = null | undefined | string | number | boolean | symbol | bigint;
|
||||
function isPrimitive(value: unknown): value is Primitive {
|
||||
return value == null || (typeof value !== 'object' && typeof value !== 'function');
|
||||
return obj;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line
|
||||
function cloneDeepHelper(obj: any, clonedObj: any): void {
|
||||
const keys = Object.keys(obj);
|
||||
export function copyProperties(target: any, source: any, stack?: Map<any, any>): void {
|
||||
const keys = Object.keys(source);
|
||||
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i];
|
||||
const descriptor = Object.getOwnPropertyDescriptor(obj, key);
|
||||
const descriptor = Object.getOwnPropertyDescriptor(source, key);
|
||||
|
||||
if ((descriptor?.writable || descriptor?.set)) {
|
||||
clonedObj[key] = cloneDeep(obj[key]);
|
||||
target[key] = cloneDeepImpl(source[key], stack);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isTypedArray(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
obj: any
|
||||
): obj is
|
||||
| Uint8Array
|
||||
| Uint8ClampedArray
|
||||
| Uint16Array
|
||||
| Uint32Array
|
||||
| BigUint64Array
|
||||
| Int8Array
|
||||
| Int16Array
|
||||
| Int32Array
|
||||
| BigInt64Array
|
||||
| Float32Array
|
||||
| Float64Array {
|
||||
return (
|
||||
obj instanceof Uint8Array ||
|
||||
obj instanceof Uint8ClampedArray ||
|
||||
obj instanceof Uint16Array ||
|
||||
obj instanceof Uint32Array ||
|
||||
obj instanceof BigUint64Array ||
|
||||
obj instanceof Int8Array ||
|
||||
obj instanceof Int16Array ||
|
||||
obj instanceof Int32Array ||
|
||||
obj instanceof BigInt64Array ||
|
||||
obj instanceof Float32Array ||
|
||||
obj instanceof Float64Array
|
||||
);
|
||||
}
|
||||
|
||||
export type Resolved<T> = Equal<T, ResolvedMain<T>> extends true ? T : ResolvedMain<T>;
|
||||
|
||||
type Equal<X, Y> = X extends Y ? (Y extends X ? true : false) : false;
|
||||
|
||||
type ResolvedMain<T> = T extends [never]
|
||||
? never // (special trick for jsonable | null) type
|
||||
: ValueOf<T> extends boolean | number | bigint | string
|
||||
? ValueOf<T>
|
||||
: T extends (...args: any[]) => any
|
||||
? never
|
||||
: T extends object
|
||||
? ResolvedObject<T>
|
||||
: ValueOf<T>;
|
||||
|
||||
type ResolvedObject<T extends object> =
|
||||
T extends Array<infer U>
|
||||
? IsTuple<T> extends true
|
||||
? ResolvedTuple<T>
|
||||
: Array<ResolvedMain<U>>
|
||||
: T extends Set<infer U>
|
||||
? Set<ResolvedMain<U>>
|
||||
: T extends Map<infer K, infer V>
|
||||
? Map<ResolvedMain<K>, ResolvedMain<V>>
|
||||
: T extends WeakSet<any> | WeakMap<any, any>
|
||||
? never
|
||||
: T extends
|
||||
| Date
|
||||
| Uint8Array
|
||||
| Uint8ClampedArray
|
||||
| Uint16Array
|
||||
| Uint32Array
|
||||
| BigUint64Array
|
||||
| Int8Array
|
||||
| Int16Array
|
||||
| Int32Array
|
||||
| BigInt64Array
|
||||
| Float32Array
|
||||
| Float64Array
|
||||
| ArrayBuffer
|
||||
| SharedArrayBuffer
|
||||
| DataView
|
||||
| Blob
|
||||
| File
|
||||
? T
|
||||
: {
|
||||
[P in keyof T]: ResolvedMain<T[P]>;
|
||||
};
|
||||
|
||||
type ResolvedTuple<T extends readonly any[]> = T extends []
|
||||
? []
|
||||
: T extends [infer F]
|
||||
? [ResolvedMain<F>]
|
||||
: T extends [infer F, ...infer Rest extends readonly any[]]
|
||||
? [ResolvedMain<F>, ...ResolvedTuple<Rest>]
|
||||
: T extends [(infer F)?]
|
||||
? [ResolvedMain<F>?]
|
||||
: T extends [(infer F)?, ...infer Rest extends readonly any[]]
|
||||
? [ResolvedMain<F>?, ...ResolvedTuple<Rest>]
|
||||
: [];
|
||||
|
||||
type IsTuple<T extends readonly any[] | { length: number }> = [T] extends [never]
|
||||
? false
|
||||
: T extends readonly any[]
|
||||
? number extends T['length']
|
||||
? false
|
||||
: true
|
||||
: false;
|
||||
|
||||
type ValueOf<Instance> =
|
||||
IsValueOf<Instance, boolean> extends true
|
||||
? boolean
|
||||
: IsValueOf<Instance, number> extends true
|
||||
? number
|
||||
: IsValueOf<Instance, string> extends true
|
||||
? string
|
||||
: Instance;
|
||||
|
||||
type IsValueOf<Instance, O extends IValueOf<any>> = Instance extends O
|
||||
? O extends IValueOf<infer Primitive>
|
||||
? Instance extends Primitive
|
||||
? false
|
||||
: true // not Primitive, but Object
|
||||
: false // cannot be
|
||||
: false;
|
||||
|
||||
interface IValueOf<T> {
|
||||
valueOf(): T;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user