How Lodash Checks Primitive and Boxed Strings

In JavaScript, strings can exist either as primitive values or as boxed String wrapper objects created via the new String() constructor. While standard JavaScript treats them differently under strict equality and type checks, the Lodash library provides unified type verification through its _.isString utility. To handle both representations accurately without mistaking general objects for strings, Lodash splits its verification process into a fast typeof evaluation for primitives and an internal object-tag evaluation for boxed objects.

The Problem with Primitive vs. Boxed Strings

In native JavaScript, primitive strings and boxed string objects exhibit distinct behaviors when examined using native operators:

const primitiveStr = 'hello';
const objectStr = new String('hello');

typeof primitiveStr; // 'string'
typeof objectStr;    // 'object'

primitiveStr === objectStr; // false

Because typeof objectStr yields 'object', a standard typeof check fails to recognize a boxed string as string data. Conversely, checking only whether an entity has string methods or properties can lead to false positives on custom objects.

How Lodash Implements isString

Lodash bridges this divide in its internal isString implementation. The source code evaluates the argument using a conditional branching strategy:

function isString(value) {
  const type = typeof value;
  return type === 'string' || (
    type === 'object' && 
    value != null && 
    !Array.isArray(value) && 
    getTag(value) === '[object String]'
  );
}

The differentiation occurs across two distinct steps:

  1. Primitive Detection via typeof:
    Lodash first evaluates typeof value === 'string'. If the value is a string literal or a primitive produced by String(...) without new, the condition evaluates to true immediately. This avoids unnecessary function calls for the most common use cases.

  2. Boxed Detection via Internal Tags:
    If the typeof check returns 'object', Lodash proceeds to verify whether the object is a boxed primitive. It confirms the value is non-null and not an array, then calls its internal getTag() helper.

The Role of getTag and Object.prototype.toString

Lodash's getTag relies on Object.prototype.toString.call(value). When JavaScript executes Object.prototype.toString on a boxed string object, it reads the internal [[Class]] slot (or Symbol.toStringTag in modern ECMAScript environments), returning the string:

Object.prototype.toString.call(new String('hello')); // '[object String]'

Standard objects, functions, and other boxed types return different signatures, such as '[object Object]' or '[object Number]'. By checking for [object String], Lodash safely verifies that the object is genuinely an instance of the String wrapper, even if it originated from a different execution context or iframe where instanceof String checks might fail.

Separating Boxed Strings from Primitives

Although _.isString returns true for both types, you can separate them by combining Lodash functions or pairing Lodash with native checks:

Boxed strings will also return false when checked with _.isPlainObject(value), ensuring that Lodash does not misidentify string objects as generic key-value dictionaries.