Important: This documentation covers Yarn 1 (Classic).
For Yarn 2+ docs and migration guide, see yarnpkg.com.

Package detail

@httpx/plain-object

belgattitude1.8kMIT2.1.7TypeScript support: included

Fast and lightweight utility functions to check if a value is a plain object.

plainObject, plain-object, is-plain-object, is-plain-obj, assert-plain-object

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.

npm changelog codecov bundles node browserslist size downloads license

Install

$ npm install @httpx/plain-object
$ yarn add @httpx/plain-object
$ pnpm add @httpx/plain-object

Features

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 object

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);
}

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.

CodSpeed Badge

 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:

  • <input checked="" disabled="" type="checkbox"> Slightly faster (10%) and lighter
  • <input checked="" disabled="" type="checkbox"> ESM and CJS formats.
  • <input checked="" disabled="" type="checkbox"> Named export.
  • <input checked="" disabled="" type="checkbox"> Smaller bundle size.
  • <input checked="" disabled="" type="checkbox"> Provide a PlainObject type and assertPlainObject function.
  • <input checked="" disabled="" type="checkbox"> Typescript convenience PlainObject type.

Since v2, it diverges from is-plain-obj by

  • <input checked="" disabled="" type="checkbox"> Static built-in classes are considered as plain objects (use isStaticBuiltInClass to exclude).
  • <input checked="" disabled="" type="checkbox"> [Symbol.iterator] is considered as a valid property for plain objects.
  • <input checked="" disabled="" type="checkbox"> [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

Jetbrains logo Jetbrains logo
JetBrains Embie.be

License

MIT © Sébastien Vanvelthem and contributors.

changelog