Fast and lightweight (~80B) functions to check or assert that a value is a plain object.
A plain object is a basic JavaScript object, such as {}
, { data: [] }
, new Object()
or Object.create(null)
.
See how it compares to other libraries.
Install
yarn add @httpx/plain-object
Features
- 👉 Provide isPlainObject and assertPlainObject functions.
- 🦄 Convenience PlainObject typescript typings.
- 🚀 Faster than most alternatives, see benchmarks.
- 📐 Lightweight (starts at ~80B)
- 🛡️ Tested on node 18-22, browser, cloudflare workers and runtime/edge.
- 🙏 Works cross-realms (node:vm runInNewContext,…)
- 🗝️ Available in ESM and CJS formats.
Documentation
👉 Official website or GitHub Readme
Usage
isPlainObject
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('({})')); // ✅
// ✅👇 Static built-in classes are treated as plain objects
// check for `isStaticBuiltInClass` to exclude if needed
isPlainObject(Math); // ✅
isPlainObject(JSON); // ✅
isPlainObject(Atomics); // ✅
// ❌👇 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); // ❌,
assertPlainObject
import { assertPlainObject } from '@httpx/plain-object';
import type { PlainObject } from '@httpx/plain-object';
function fn(value: unknown) {
// 👇 Throws `new TypeError('Not a PlainObject')` 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);
}
isStaticBuiltInClass
info: Since v2.0.0
Since v2.0.0, isPlainObject
will accept static built-in classes
as plain objects (Math, JSON, Atomics). If you need to exclude them,
a new typeguard has been created isStaticBuiltInClass
.
import { isPlainObject, isStaticBuiltInClass } from '@httpx/plain-object';
const v = Math; // or Atomics or JSON
if (isPlainObject(v) && !isStaticBuiltInClass(v)) {
console.log('v is a plain object but not a static built-in class');
}
PlainObject type
Generic
ìsPlainObject
and assertPlainObject
accepts a generic to provide type
autocompletion. Be aware that no runtime check are done. If you’re looking for
runtime validation, check zod, valibot or other alternatives.
import { isPlainObject } from '@httpx/plain-object';
import type { PlainObject } from '@httpx/plain-object';
type CustomType = {
id: number;
data?: {
test: string[];
attributes?: {
url?: string | null;
caption?: string | null;
alternativeText?: string | null;
} | null;
} | null;
};
const value = { id: 1 } as unknown;
if (isPlainObject<CustomType>(value)) {
// ✅ Value is a PlainObject with typescript autocompletion
// Note that there's no runtime checking of keys, so they are
// `unknown | undefined`. They will require unsing `?.` to access.
const url = value?.data?.attributes?.url; // autocompletion works
// ✅ url is `unknown | undefined`, so in order to use it, you'll need to
// manually check for the type.
if (typeof url === 'string') {
console.log(url.toUpperCase());
}
}
PlainObject
import { assertPlainObject } from '@httpx/plain-object';
import type { PlainObject } from '@httpx/plain-object';
function someFn(value: PlainObject) {
//
}
const value = { key: 'value' } as unknown;
assertPlainObject(value);
someFn(value)
Benchmarks
Performance is continuously monitored thanks to codspeed.io.
RUN v3.0.4 /home/sebastien/github/httpx/packages/plain-object
✓ bench/comparative.bench.ts > Compare calling isPlainObject with 110x mixed types values 5684ms
name hz min max mean p75 p99 p995 p999 rme samples
· "@httpx/plain-object": `isPlainObject(v)` 1,832,016.65 0.0004 0.4702 0.0005 0.0005 0.0009 0.0011 0.0018 ±0.49% 916009 fastest
· "is-plain-obj":"4.1.0": 'isPlainObj(v)' 1,565,569.05 0.0005 0.5879 0.0006 0.0006 0.0014 0.0015 0.0018 ±0.53% 782785
· "@sindresorhus/is":"7.0.1": 'is.plainObject(v)' 944,124.29 0.0008 0.5417 0.0011 0.0010 0.0017 0.0023 0.0032 ±0.38% 472063
· "es-toolkit":"1.31.0": 'isPlainObject(v)' 1,432,993.92 0.0005 0.4105 0.0007 0.0007 0.0012 0.0016 0.0028 ±0.56% 716497
· "redux":"5.0.1": 'isPlainObject(v)' 651,167.40 0.0012 0.7868 0.0015 0.0015 0.0021 0.0028 0.0071 ±0.55% 325584
· "is-plain-object":"5.0.0": 'isPlainObject(v)' 828,601.23 0.0010 0.7233 0.0012 0.0012 0.0022 0.0028 0.0106 ±0.68% 414301
· lodash-es:"4.17.21": '_.isPlainObject(v)' 23,543.74 0.0332 0.6199 0.0425 0.0399 0.1037 0.1168 0.2867 ±0.80% 11772 slowest
BENCH Summary
"@httpx/plain-object": `isPlainObject(v)` - bench/comparative.bench.ts > Compare calling isPlainObject with 110x mixed types values
1.17x faster than "is-plain-obj":"4.1.0": 'isPlainObj(v)'
1.28x faster than "es-toolkit":"1.31.0": 'isPlainObject(v)'
1.94x faster than "@sindresorhus/is":"7.0.1": 'is.plainObject(v)'
2.21x faster than "is-plain-object":"5.0.0": 'isPlainObject(v)'
2.81x faster than "redux":"5.0.1": 'isPlainObject(v)'
77.81x faster than lodash-es:"4.17.21": '_.isPlainObject(v)'
See benchmark file for details.
Bundle size
Bundle size is tracked by a size-limit configuration
Scenario (esm) | Size (compressed) |
---|---|
import { isPlainObject } from '@httpx/plain-object | ~ 80B |
import { assertPlainObject } from '@httpx/plain-object | ~ 134B |
Both isPlainObject and assertPlainObject | ~ 142B |
import { isStaticBuiltInClass } from '@httpx/plain-object | ~ 37B |
For CJS usage (not recommended) track the size on bundlephobia.
Compatibility
Level | CI | Description |
---|---|---|
Node | ✅ | CI for 18.x, 20.x & 22.x. |
Browser | ✅ | Tested with latest chrome (vitest/playwright) |
Browsers | ✅ | > 96% on 07/2024. Mins to Chrome 96+, Firefox 90+, Edge 19+, iOS 12+, Safari 12+, Opera 77+ |
Edge | ✅ | Ensured on CI with @vercel/edge-runtime. |
Cloudflare | ✅ | Ensured with @cloudflare/vitest-pool-workers (see wrangler.toml |
Typescript | ✅ | TS 5.0 + / are-the-type-wrong checks on CI. |
ES2022 | ✅ | Dist files checked with es-check |
Performance | ✅ | Monitored with codspeed.io |
For older browsers: most frontend frameworks can transpile the library (ie: nextjs…)
Comparison with other libraries
Library | Compat | Perf | CJS+ESM |
---|---|---|---|
is-plain-obj | Differences | 1.17x slower | No |
es-toolkit | No | 1.25x slower | Yes |
(@redux)isPlainObject | ✅ 100% | 2.76x slower | Yes |
(lodash)isPlainObject | No | 83.50x slower | No |
redux/isPlainObject
100% compatible see tests.
@sindresorhus/is-plain-obj
This library wouldn’t be possible without @sindresorhus is-plain-obj. Notable differences:
- Slightly faster (10%)
- ESM and CJS formats.
- Named export.
- Smaller bundle size.
- Provide a
PlainObject
type andassertPlainObject
function. - Typescript convenience
PlainObject
type.
Since v2, it diverges from is-plain-obj
by
- Static built-in classes are considered as plain objects (use isStaticBuiltInClass to exclude).
-
[Symbol.iterator]
is considered as a valid property for plain objects. -
[Symbol.toStringTag]
is considered as a valid property for plain objects.`