What is a Plain Old JavaScript Object (POJO)?

Oct 8, 2019

There's a lot of debate as to what a POJO is in JavaScript: StackOverflow thinks it is any class that contains user data, but the top npm module on Google defines a POJO as any object whose prototype is Object.prototype.

The intuition behind POJOs is that a POJO is an object that only contains data, as opposed to methods or internal state. Most JavaScript codebases consider objects created using curly braces {} to be POJOs. However, more strict codebases sometimes create POJOs by calling Object.create(null) to avoid inheriting from the built-in Object class.

// If you create an object `obj` with `{}`, `obj` is an instance of
// the `Object` class, and so it has some built-in properties.
let obj = {};
obj.hasOwnProperty; // [Function]
obj.constructor === Object; // true

// On the other hand, `Object.create(null)` creates an object that
// doesn't inherit from **any** class.
obj = Object.create(null);
typeof obj; // 'object'
obj.hasOwnProperty; // undefined
obj.constructor; // undefined

obj.prop = 42;
obj.prop; // 42

POJOs vs Maps

JavaScript Maps are an alternative to POJOs for storing data because they do not have any inherited keys from the Object class. However, objects are generally easier to work with than maps, because not all JavaScript functions, frameworks, and libraries support maps. For example, the JSON.stringify() function doesn't serialize maps by default.

const map = new Map([['answer', 42]]);
JSON.stringify(map); // '{}'

Checking if an Object is a POJO

Checking if an object is a POJO can be somewhat tricky and depends on whether you consider objects created using Object.create(null) to be POJOs. The safest way is using the Object.getPrototypeOf() function and comparing the object's prototype.

function isPOJO(arg) {
  if (arg == null || typeof arg !== 'object') {
    return false;
  }
  const proto = Object.getPrototypeOf(arg);
  if (proto == null) {
    return true; // `Object.create(null)`
  }
  return proto === Object.prototype;
}

isPOJO({}); // true
isPOJO(Object.create(null)); // true
isPOJO(null); // false
isPOJO(new Number(42)); // false

For example, below is Mongoose's internal isPOJO() function

exports.isPOJO = function isPOJO(arg) {
  if (arg == null || typeof arg !== 'object') {
    return false;
  }
  const proto = Object.getPrototypeOf(arg);
  // Prototype may be null if you used `Object.create(null)`
  // Checking `proto`'s constructor is safe because `getPrototypeOf()`
  // explicitly crosses the boundary from object data to object metadata
  return !proto || proto.constructor.name === 'Object';
};

Mongoose checks for the constructor.name property instead of checking if proto.constructor === Object to support objects generated using Node.js vm module.

const obj = require('vm').runInNewContext('({})');
// `obj` inherits from a different JavaScript context's `Object` class.
obj.constructor === Object; // false
obj.constructor.name; // 'Object'

Did you find this tutorial useful? Say thanks by starring our repo on GitHub!

More Fundamentals Tutorials