Превратить объект в булевое значение — задача почти всегда переформулируется как: «есть ли в нём что-то полезное?». Хотим получить true, если объект «живой» (содержит нужные поля), и false, если он пустой или равен null.

Главная сложность: прямой Boolean(obj) для любого объекта (включая пустой {}) возвращает true. Это потому, что любой объект — truthy по правилам стандарта. Поэтому для осмысленной проверки нужно идти глубже.

Boolean({})           // true
Boolean({ a: 1 })     // true
Boolean(null)         // false  (null — единственное исключение среди «объектов»)
Boolean(undefined)    // false

// прямой Boolean плохо отличает «есть данные» от «нет»:
let user = {};
if (user) {
    console.log('зайдёт всегда, даже если объект пустой');
}

Когда это нужно

  • Проверка конфига. Опциональный объект настроек: если передали — используем, если нет — берём дефолт. const options = userOptions ?? DEFAULT_OPTIONS. Но если ждём «не просто переданный, а с реальными полями внутри», нужна более глубокая проверка.
  • Conditional rendering. В шаблоне Vue/React: {user && <Profile user={user}/>} — здесь хочется не рендерить пустую карточку, если у пользователя только id, но больше ничего нет.
  • Feature-flag check. Объект flags с включёнными фичами: пустой — ничего не включено, заполненный — есть что показать.
  • Валидация формы. Объект errors накапливает ошибки полей. Пустой — форма валидна, заполненный — нет.
  • Опциональные поля API. Бэк может прислать { pricing: {} } или { pricing: null } для «ценообразования нет». Хочется одинаково обработать оба случая.

Способ 1: Object.keys + длина

Самый прямой способ — посчитать собственные ключи объекта. Object.keys(obj) возвращает массив, его .length даёт количество собственных перечисляемых свойств.

function hasContent(obj) {
    if (obj == null) return false;          // null или undefined → false
    return Object.keys(obj).length > 0;
}

hasContent({})              // false
hasContent({ a: 1 })        // true
hasContent(null)            // false
hasContent(undefined)       // false

// в одну строку через ?? и optional chaining:
const isFilled = !!(obj && Object.keys(obj).length);

Запись obj == null (с двойным равно) ловит и null, и undefined — это один из редких случаев, где нестрогое сравнение читабельнее ===.

Способ 2: проверка конкретных полей

Часто «живой» объект — не «любой с хотя бы одним ключом», а «имеющий определённые поля». Например, пользователь считается осмысленным, если у него есть id и name.

function isValidUser(user) {
    return user != null
        && typeof user === 'object'
        && 'id' in user
        && 'name' in user
        && user.name.length > 0;
}

isValidUser({})                              // false
isValidUser({ id: 1 })                       // false (нет name)
isValidUser({ id: 1, name: '' })             // false (пустое имя)
isValidUser({ id: 1, name: 'Сергей' })       // true

Оператор in проверяет наличие свойства (даже если оно undefined). Альтернатива — user.id !== undefined, но in точнее: он отличает «поля нет» от «поле есть, но undefined».

Способ 3: Object.values + some

Если объект может содержать пустые значения, но «живым» считается, только когда хотя бы одно — не пустое:

function hasAnyValue(obj) {
    if (obj == null) return false;
    return Object.values(obj).some(v => v != null && v !== '');
}

hasAnyValue({})                              // false
hasAnyValue({ a: null, b: undefined })       // false
hasAnyValue({ a: null, b: 'данные' })        // true
hasAnyValue({ a: 0 })                        // true (ноль — это значение)

Это та же логика, что и для массивов: смотрим не «есть ли структурно что-то», а «есть ли осмысленные данные».

Подводные камни

typeof null === "object". Главная ловушка. null сообщает свой тип как "object" — это историческая ошибка стандарта. Если в коде есть проверка typeof x === "object", она «поймает» и null, что обычно нежелательно. Всегда добавляй x !== null:

function isObject(x) {
    return x !== null && typeof x === 'object';
}

isObject(null)              // false  ✓
isObject({})                // true
isObject([])                // true   (массив — тоже объект)

Массив — это тоже объект. typeof [1, 2] === "object", и Object.keys([1, 2]) вернёт ["0", "1"] (индексы как строки). Если функция должна работать только с обычными объектами, исключай массивы через !Array.isArray(x).

Object.keys видит только собственные ключи. Если объект создан через Object.create(parent) — унаследованные поля не попадут в Object.keys. Для проверки всех (включая прототип) используется Reflect.ownKeys(obj) или ручной обход через for...in.

hasOwnProperty и Object.create(null). Объект, созданный через Object.create(null), не имеет .hasOwnProperty в прототипе — попытка вызова упадёт. Безопасный способ: Object.prototype.hasOwnProperty.call(obj, key) или современный Object.hasOwn(obj, key) (ES2022).

Symbol-ключи невидимы для Object.keys. Если объект содержит только Symbol-ключи (редкий случай, но возможный в библиотеках), Object.keys(obj).length вернёт ноль, даже если поля есть. Для полного списка используется Reflect.ownKeys(obj), который видит и строковые, и Symbol-ключи.

Прокси-объекты могут лгать. Proxy позволяет перехватить любое чтение. Объект new Proxy(target, handler) на Object.keys может вернуть [], даже если в реальном target что-то есть. Это очень редкий случай в обычном коде, но в библиотеках вроде Vue (реактивные объекты были прокси в Vue 3) встречается.