@httpx/plain-object
Version:
Fast and lightweight utility functions to check if a value is a plain object.
109 lines (103 loc) • 4.06 kB
text/typescript
type PlainObjectKey = string | number | symbol;
type BasePlainObject = Record<PlainObjectKey, unknown>;
interface DefaultBasePlainObject extends BasePlainObject {
readonly __tag: 'default-plain-object';
}
type Simplify<T> = {
[P in keyof T]: T[P];
} & NonNullable<unknown>;
type PlainObjectDeepPartialUnknown<T> = {
[P in keyof T]?: NonNullable<T[P]> extends BasePlainObject ? Simplify<PlainObjectDeepPartialUnknown<NonNullable<T[P]>>> : unknown;
};
type MsgOrErrorFactory = string | (() => Error);
type PlainObject<TValue extends BasePlainObject = DefaultBasePlainObject> = TValue extends DefaultBasePlainObject ? Record<PlainObjectKey, unknown> : Simplify<PlainObjectDeepPartialUnknown<TValue>>;
/**
* Assert a value is a plain object
*
* @example
* ```typescript
* import { assertPlainObject } from '@httpx/plain-object';
* import type { PlainObject } from '@httpx/plain-object';
*
* function fn(value: unknown) {
*
* // 👇 Throws `new TypeError('Not a plain object')` if not a plain object
* assertPlainObject(value);
*
* // 👇 Throws `new TypeError('Custom message')` if not a plain object
* assertPlainObject(value, 'Custom message');
*
* // 👇 Throws custom error if not a plain object
* assertPlainObject(value, () => {
* throw new HttpBadRequest('Custom message');
* });
*
* return value;
* }
*
* try {
* const value = fn({ key: 'value' });
* // ✅ Value is known to be PlainObject<unknown>
* assertType<PlainObject>(value);
* } catch (error) {
* console.error(error);
* }
* ```
*
* @throws TypeError
*/
declare function assertPlainObject<TValue extends BasePlainObject = DefaultBasePlainObject>(v: unknown, msgOrErrorFactory?: MsgOrErrorFactory): asserts v is TValue extends DefaultBasePlainObject ? BasePlainObject : PlainObject<TValue>;
/**
* Check if a value is a plain object
*
* A plain object is a basic JavaScript object, such as {}, { data: [] }, new Object() or Object.create(null).
*
* @example
* ```typescript
* import { isPlainObject } from '@httpx/plain-object';
*
* // ✅👇 True
*
* isPlainObject({ }); // ✅
* isPlainObject({ key: 'value' }); // ✅
* isPlainObject({ key: new Date() }); // ✅
* isPlainObject(new Object()); // ✅
* isPlainObject(Object.create(null)); // ✅
* isPlainObject({ nested: { key: true} }); // ✅
* isPlainObject(new Proxy({}, {})); // ✅
* isPlainObject({ [Symbol('tag')]: 'A' }); // ✅
*
* // ✅👇 (node context, workers, ...)
* const runInNewContext = await import('node:vm').then(
* (mod) => mod.runInNewContext
* );
* isPlainObject(runInNewContext('({})')); // ✅
*
* // ❌👇 False
*
* class Test { };
* isPlainObject(new Test()) // ❌
* isPlainObject(10); // ❌
* isPlainObject(null); // ❌
* isPlainObject('hello'); // ❌
* isPlainObject([]); // ❌
* isPlainObject(new Date()); // ❌
* isPlainObject(new Uint8Array([1])); // ❌
* isPlainObject(Buffer.from('ABC')); // ❌
* isPlainObject(Promise.resolve({})); // ❌
* isPlainObject(Object.create({})); // ❌
* isPlainObject(new (class Cls {})); // ❌
* isPlainObject(globalThis); // ❌
*
* // ✅👇 Note that static built-in classes are treated as plain objects
* // check for `isStaticBuiltInClass` to exclude if needed
*
* isPlainObject(Math); // ✅
* isPlainObject(JSON); // ✅
* isPlainObject(Atomics); // ✅
* ```
*/
declare const isPlainObject: <TValue extends BasePlainObject = DefaultBasePlainObject>(v: unknown) => v is TValue extends DefaultBasePlainObject ? BasePlainObject : PlainObject<TValue>;
type StaticBuiltInClass = Math | JSON | Atomics;
declare const isStaticBuiltInClass: (v: unknown) => v is StaticBuiltInClass;
export { type PlainObject, type StaticBuiltInClass, assertPlainObject, isPlainObject, isStaticBuiltInClass };