@httpx/plain-object
v2.1.7
Published
Fast and lightweight utility functions to check if a value is a plain object.
Maintainers
Readme
@httpx/plain-object
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
$ npm install @httpx/plain-object
$ yarn add @httpx/plain-object
$ pnpm add @httpx/plain-objectFeatures
- 👉 Provide isPlainObject and assertPlainObject functions.
- 🦄 Convenience PlainObject typescript typings.
- 🚀 Faster than most alternatives, see benchmarks.
- 📐 Lightweight (starts at ~80B)
- 🛡️ Tested on node 20-25, bun, 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('({})')); // ✅
// ❌👇 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 {})); // ❌
// ⚠️ Edge cases
//
// 👇 globalThis isn't properly portable across all JS environments
//
isPlainObject(globalThis); // ✅ with Bun ❌ otherwise (browser, Nodejs, edge, cloudflare)
// 👇 Static built-in classes aren't properly checked. This is a trade-off
// to maintain the best performance and size. If you need to check for these,
// use a custom type guard. But in most cases, you won't need to check for these
// as the probability of writing a code that receives these as plain objects is low.
// and probably indicates an issue in your code.
isPlainObject(Math); // ⚠️✅ return true, but Math is not a plain object
isPlainObject(JSON); // ⚠️✅ return true, but JSON is not a plain object
isPlainObject(Atomics); // ⚠️✅ return true, but Atomics is not a plain object
isPlainObject(Reflect); // ⚠️✅ return true, but Reflect is not a plain objectassertPlainObject
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);
}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.2.4 /home/sebastien/github/httpx/packages/plain-object
✓ bench/comparative.bench.ts > Compare calling isPlainObject with 110x mixed types values 6594ms
name hz min max mean p75 p99 p995 p999 rme samples
· "@httpx/plain-object": `isPlainObject(v)` 932,629.62 0.0008 0.7244 0.0011 0.0010 0.0019 0.0021 0.0103 ±0.55% 466316
· "is-plain-obj":"4.1.0": 'isPlainObj(v)' 891,373.92 0.0009 1.0045 0.0011 0.0010 0.0019 0.0022 0.0149 ±1.00% 445687
· "@sindresorhus/is":"7.1.0": 'is.plainObject(v)' 343,401.88 0.0022 1.1724 0.0029 0.0024 0.0052 0.0079 0.0489 ±1.50% 171706
· "es-toolkit":"1.41.0": 'isPlainObject(v)' 776,704.94 0.0010 0.9174 0.0013 0.0011 0.0022 0.0024 0.0242 ±1.03% 388353
· "redux":"5.0.1": 'isPlainObject(v)' 316,795.75 0.0024 0.7996 0.0032 0.0029 0.0053 0.0084 0.0370 ±0.91% 158461
· "is-plain-object":"5.0.0": 'isPlainObject(v)' 374,471.38 0.0021 1.8234 0.0027 0.0027 0.0056 0.0061 0.0272 ±1.00% 187236
· "immer/is-plain-object":"4.2.0": 'isPlainObject(v)' 310,784.72 0.0026 0.4656 0.0032 0.0033 0.0069 0.0081 0.0267 ±0.51% 155393
· lodash-es:"4.17.21": '_.isPlainObject(v)' 12,787.05 0.0662 1.5537 0.0782 0.0766 0.1903 0.2622 0.5214 ±1.13% 6394
BENCH Summary
"@httpx/plain-object": `isPlainObject(v)` - bench/comparative.bench.ts > Compare calling isPlainObject with 110x mixed types values
1.05x faster than "is-plain-obj":"4.1.0": 'isPlainObj(v)'
1.20x faster than "es-toolkit":"1.41.0": 'isPlainObject(v)'
2.49x faster than "is-plain-object":"5.0.0": 'isPlainObject(v)'
2.72x faster than "@sindresorhus/is":"7.1.0": 'is.plainObject(v)'
2.94x faster than "redux":"5.0.1": 'isPlainObject(v)'
3.00x faster than "immer/is-plain-object":"4.2.0": 'isPlainObject(v)'
72.94x 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 | ~ 133B |
| Both isPlainObject and assertPlainObject | ~ 141B |
For CJS usage (not recommended) track the size on bundlephobia.
Compatibility
| Level | CI | Description |
|--------------|-----|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Node | ✅ | CI for 20.x, 22.x, 24.x & 25.x. |
| Browser | ✅ | Tested with latest chrome (vitest/playwright) |
| Browserslist | ✅ | > 95% on 01/2025. defaults, chrome >= 96, firefox >= 105, edge >= 113, safari >= 15, ios >= 15, opera >= 103, not dead |
| Bun | ✅ | Tested with latest (at time of writing >= 1.3.3) |
| 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:
- [x] Slightly faster (10%) and lighter
- [x] ESM and CJS formats.
- [x] Named export.
- [x] Smaller bundle size.
- [x] Provide a
PlainObjecttype andassertPlainObjectfunction. - [x] Typescript convenience
PlainObjecttype.
Since v2, it diverges from is-plain-obj by
- [x] Static built-in classes are considered as plain objects (use isStaticBuiltInClass to exclude).
- [x]
[Symbol.iterator]is considered as a valid property for plain objects. - [x]
[Symbol.toStringTag]is considered as a valid property for plain objects.`
Contributors
Contributions are welcome. Have a look to the CONTRIBUTING document.
Sponsors
If my OSS work brightens your day, let's take it to new heights together! Sponsor, coffee, or star – any gesture of support fuels my passion to improve. Thanks for being awesome! 🙏❤️
Special thanks to
License
MIT © Sébastien Vanvelthem and contributors.
