Строка — самый частый источник «значений, которых нет». Юзер оставил поле пустым — пришла "". Тот же юзер напечатал null в текстовое поле — пришла строка "null". Из URL ?filter= — снова пустая строка. Семантически во всех трёх случаях значения нет, но JavaScript видит обычные строки. Чтобы дальше работать с этим как с настоящим отсутствием, нужна конверсия.

Задача: проверить условие на строке и, если оно сработало, заменить её на null, undefined или (реже) NaN.

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

  • Чистка ввода формы. Перед отправкой на бэк пустые поля стоит явно занулить: { city: "" } и { city: null } могут трактоваться по-разному (особенно если у поля есть валидатор «обязательно»).
  • Восстановление из localStorage. localStorage.getItem('missing') на отсутствующем ключе вернёт null, но если когда-то сохранил "null" строкой — при чтении получишь буквальный текст. Нормализуем.
  • URL-параметры. URLSearchParams.get('q') для отсутствующего ключа — null, но для ?q= — пустая строка. Часто оба случая хочется обрабатывать одинаково.
  • API нормализация. Бэк ждёт "discount": null для «скидки нет», но фронт собирает значение из текстового поля. Перед отправкой превращаем "" в null.
  • Сравнения и filtering. Удалить из списка все элементы «без имени» легче, когда они все имеют name === null, а не «иногда null, иногда пустая строка».

Способ 1: тернарный оператор с проверкой длины

Самый компактный и понятный способ — проверить, осталось ли что-то после trim():

let city = userInput.trim();
city = city.length ? city : null;

console.log(''.trim().length);          // 0  → null
console.log('  '.trim().length);        // 0  → null   (только пробелы)
console.log('Берлин'.trim().length);    // 6  → "Берлин"

.trim() убирает пробельные символы (включая табы и переносы строк) по краям строки. Если после этого length равно нулю — считаем, что значения нет. Иначе оставляем как есть.

Альтернатива — проверять не .length, а саму строку. "" — falsy, любая непустая — truthy:

let city = userInput.trim();
city = city || null;

// то же через ?? — но ?? тут не подходит, потому что пустая строка не nullish

Запись с || читается ещё короче, но имеет нюанс: она работает только если «нет данных» всегда falsy. Если в логике «0» или строка "0" — валидные значения, — первая запись через .length безопаснее.

Способ 2: маппинг через объект

Иногда нужно превратить в спецзначение не пустую строку, а буквальные слова "null", "undefined", "nan" — такое часто прилетает из URL или старых API.

const SPECIAL_VALUES = {
    'null': null,
    'undefined': undefined,
    'nan': NaN,
    '': null,
};

function parseValue(str) {
    const key = String(str).trim().toLowerCase();
    return key in SPECIAL_VALUES ? SPECIAL_VALUES[key] : str;
}

parseValue("null")        // null
parseValue("UNDEFINED")   // undefined
parseValue("NaN")         // NaN
parseValue("")            // null
parseValue("Берлин")      // "Берлин"

Преимущество объекта-маппинга — декларативность: добавить новый случай (например, "none"null) — одна строка. Минус — чуть больше кода для простой задачи.

Способ 3: JSON.parse для строгих случаев

Если приходящая строка должна быть валидным JSON-значением, можно использовать JSON.parse — он распознает "null" и числа автоматически.

JSON.parse("null")        // null
JSON.parse("42")          // 42
JSON.parse("true")        // true
JSON.parse('"hello"')     // "hello"
JSON.parse("hello")       // SyntaxError — не валидный JSON

// безопасная обёртка:
function tryParse(str) {
    try {
        return JSON.parse(str);
    } catch {
        return str;
    }
}

tryParse("null")          // null
tryParse("42")            // 42
tryParse("Берлин")        // "Берлин" (не валидный JSON — вернули как есть)

Этот способ хорош, когда работаешь с уже как-бы-сериализованными значениями — например, читаешь свои же данные из localStorage, которые туда положил через JSON.stringify. Тогда обратное чтение через JSON.parse корректно восстановит и null, и числа, и булевые.

Внимание: JSON.parse("undefined") бросит SyntaxErrorundefined не входит в спецификацию JSON.

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

Boolean("")false, но Boolean(" ")true. Пустая строка — falsy, строка из одного пробела — уже truthy (не пустая по длине). Всегда применяй .trim() перед проверками.

Регистр имеет значение. "Null", "NULL" и "null" — три разные строки. При маппинге всегда нормализуй через .toLowerCase() или используй регулярное выражение с флагом i.

Не-видимые пробельные символы. Юникод содержит много пробельных символов, помимо обычного ' ': неразрывный ' ', табы, zero-width space. Большинство ловит .trim(), но не все. Для тотальной нормализации используют регэксп .replace(/^\s+|\s+$/g, '')\s в JS-регэкспах поведенчески шире, чем у .trim().

HTML-сущности. Если строка пришла из innerHTML, в ней могут быть   (неразрывный пробел в виде сущности). Сначала декодируй, потом проверяй на «пустоту». Браузер декодирует автоматически при чтении textContent, но не innerHTML.

String(null) — это "null". Если случайно превратишь null в строку (например, через шаблонный литерал `${value}`), а потом проверишь «пустая или нет» — пройдёт, как непустая. Превращай в спецзначение до того, как значение успело попасть в шаблон или конкатенацию.