@httpx/plain-object

Fast and lightweight (~100B) functions to check or assert that a value is a plain object.

Install

yarn add @httpx/plain-object

Features

Documentation

👉 Official website (opens in a new tab) or Github Readme (opens in a new tab)

Usage

isPlainObject

import { isPlainObject } from '@httpx/plain-object';
 
// ✅👇 True
 
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(Math);                // ❌ Static built-in classes
isPlainObject(Promise.resolve({})); // ❌
isPlainObject(Object.create({}));   // ❌

assertPlainObject

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

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 (opens in a new tab).

CodSpeed Badge (opens in a new tab)

 RUN  v2.0.5 /home/sebastien/github/httpx/packages/plain-object

 ✓ bench/comparative.bench.ts (6) 4778ms
   ✓ Compare calling isPlainObject with 100x mixed types values (6) 4779ms
     name                                                           hz     min      max    mean     p75     p99    p995    p999     rme  samples
   · @httpx/plain-object: `isPlainObject(v)`              1,494,047.57  0.0005   8.3748  0.0007  0.0007  0.0008  0.0009  0.0047  ±3.40%   747024   fastest
   · (sindresorhus/)is-plain-obj: `isPlainObj(v)`         1,314,933.16  0.0005  11.7854  0.0008  0.0007  0.0014  0.0015  0.0022  ±6.83%   657467
   · @sindresorhus/is: `is.plainObject(v)`                  934,442.37  0.0009   2.1268  0.0011  0.0011  0.0015  0.0018  0.0065  ±1.38%   467222
   · estoolkit:  `isPlainObject(v)`                         378,403.92  0.0020  10.3395  0.0026  0.0026  0.0035  0.0055  0.0155  ±4.23%   189202
   · (jonschlinkert/)is-plain-object: `isPlainObject(v)`    629,387.99  0.0012  13.2170  0.0016  0.0015  0.0023  0.0030  0.0129  ±6.81%   314694
   · lodash-es: `_.isPlainObject(v)`                         21,164.79  0.0361  11.2577  0.0472  0.0446  0.1057  0.1678  0.5020  ±5.03%    10583   slowest


 BENCH  Summary

  @httpx/plain-object: `isPlainObject(v)` - bench/comparative.bench.ts > Compare calling isPlainObject with 100x mixed types values
    1.14x faster than (sindresorhus/)is-plain-obj: `isPlainObj(v)`
    1.60x faster than @sindresorhus/is: `is.plainObject(v)`
    2.37x faster than (jonschlinkert/)is-plain-object: `isPlainObject(v)`
    3.95x faster than estoolkit:  `isPlainObject(v)`
    70.59x faster than lodash-es: `_.isPlainObject(v)`

See benchmark file (opens in a new tab) for details.

Bundle size

Bundle size is tracked by a size-limit configuration (opens in a new tab)

Scenario (esm)Size (compressed)
import { isPlainObject } from '@httpx/plain-object~ 100B
import { assertPlainObject } from '@httpx/plain-object~ 160B
isPlainObject + assertPlainObject~ 170B

For CJS usage (not recommended) track the size on bundlephobia (opens in a new tab).

Compatibility

LevelCIDescription
NodeCI for 18.x, 20.x & 22.x.
Browsers> 96% (opens in a new tab) on 07/2024. Mins to Chrome 96+, Firefox 90+, Edge 19+, iOS 12+, Safari 12+, Opera 77+ (opens in a new tab)
EdgeEnsured on CI with @vercel/edge-runtime (opens in a new tab).
TypescriptTS 5.0 + / are-the-type-wrong (opens in a new tab) checks on CI.
ES2022Dist files checked with es-check (opens in a new tab)
PerformanceMonitored with codspeed.io (opens in a new tab)

For older browsers: most frontend frameworks can transpile the library (ie: nextjs (opens in a new tab)...)

Credits

This library wouldn't be possible without @sindresorhus (opens in a new tab) is-plain-obj (opens in a new tab). It passes the same test suite and should be 100% compatible with it. Notable differences:

  • Slighly smaller bundle and performance.
  • Named export.
  • Provide a PlainObject type and assertPlainObject function.
  • Typescript convenience PlainObject type.
  • ESM and CJS formats.