@IsSurrogatePair

Unicode в JavaScript основан на UTF-16, где символы вне базовой многоязычной плоскости (BMP) кодируются парой 16-битных значений — суррогатной парой. Это напрямую влияет на работу со строками, содержащими эмодзи, редкие иероглифы, математические символы и исторические письменности.

В UTF-16 каждый символ может занимать 1 или 2 16-битных элемента:

  • BMP (U+0000 — U+FFFF) — представляется одним 16-битным словом

  • Дополнительные плоскости (U+10000 — U+10FFFF) — кодируются двумя 16-битными значениями:

    • старший суррогат (high surrogate)
    • младший суррогат (low surrogate)

Такая пара называется суррогатной парой.

Пример эмодзи ?:

  • Unicode: U+1F604
  • UTF-16: 0xD83D 0xDE04 (суррогатная пара)

В строках JavaScript это важно, потому что length, индексация и многие операции работают с 16-битными кодовыми единицами, а не с полноценными Unicode-символами.

Декоратор @IsSurrogatePair

В библиотеке валидации class-validator декоратор @IsSurrogatePair предназначен для проверки строк, содержащих корректные суррогатные пары UTF-16.

Основная цель проверки — убедиться, что строка включает символы, представленные именно через суррогатные пары, и не содержит повреждённых или разорванных кодовых единиц.

Декоратор применяется к строковым свойствам класса и участвует в процессе валидации DTO или моделей данных.

Поведение и логика проверки

@IsSurrogatePair выполняет анализ строки на уровне UTF-16 последовательности:

  • проверяется наличие корректных high surrogate (0xD800–0xDBFF)
  • проверяется наличие соответствующих low surrogate (0xDC00–0xDFFF)
  • проверяется их корректное расположение (high + low)
  • исключаются одиночные суррогаты

Ключевое свойство проверки:

строка должна содержать валидные пары, а не разорванные суррогаты

Некорректные случаи:

  • только high surrogate без low surrogate
  • только low surrogate
  • перепутанный порядок
  • изолированные суррогатные кодовые единицы

Базовое использование

import { IsSurrogatePair } from 'class-validator';

class EmojiDto {
  @IsSurrogatePair()
  emoji: string;
}

В этом случае поле emoji должно содержать строку, в которой присутствует корректная UTF-16 суррогатная пара.

Пример валидных значений

const dto = {
  emoji: '?'
};

Эмодзи представляется суррогатной парой и проходит валидацию.

Другие примеры:

  • ?
  • ?
  • ?
  • ? (в зависимости от кодировки и представления)

Пример невалидных значений

const dto = {
  emoji: '\uD83D' // только high surrogate
};
const dto = {
  emoji: '\uDE04' // только low surrogate
};
const dto = {
  emoji: '\uDE04\uD83D' // перепутанный порядок
};

Такие строки считаются некорректными UTF-16 последовательностями и не проходят проверку.

Контекст применения

Проверка суррогатных пар используется в ситуациях, где важна целостность Unicode-данных:

  • обработка пользовательских сообщений с эмодзи
  • системы чатов и реакций
  • хранение символов вне BMP
  • API, принимающие текстовые данные с расширенной Unicode-лексикой
  • интеграции с внешними источниками, где возможна порча UTF-16 последовательностей

Особенности поведения в JavaScript

JavaScript строки не хранят символы как Unicode-скаляры, а используют UTF-16 кодовые единицы. Это приводит к ряду особенностей:

'?'.length // 2

Хотя визуально это один символ, фактически это две кодовые единицы.

@IsSurrogatePair учитывает именно этот уровень представления, а не абстрактные Unicode-кодпоинты.

Разница с валидацией Unicode-символов

Суррогатная пара — это не просто “эмодзи” или “не-BMP символ”. Проверка фокусируется на корректности UTF-16 представления, а не на семантике символа.

Возможные различия:

  • строка может содержать валидный Unicode-символ, но быть некорректной как UTF-16 последовательность
  • строка может быть технически валидной UTF-16, но не содержать суррогатных пар вообще

Взаимодействие с другими декораторами

@IsSurrogatePair часто комбинируется с другими проверками строк:

import { IsString, IsNotEmpty, IsSurrogatePair } from 'class-validator';

class ReactionDto {
  @IsString()
  @IsNotEmpty()
  @IsSurrogatePair()
  reaction: string;
}

Такая комбинация задаёт строгие требования:

  • значение должно быть строкой
  • строка не должна быть пустой
  • строка должна содержать корректные суррогатные пары UTF-16

Типичные ошибки при использовании

1. Ожидание проверки “на эмодзи”

Суррогатная пара не эквивалентна “наличию эмодзи”. Некоторые эмодзи могут быть составными последовательностями (ZWJ-sequences), которые не всегда сводятся к одной суррогатной паре.

2. Игнорирование составных символов

Некоторые символы представляют собой комбинации нескольких Unicode-точек:

  • ?‍?‍?‍?
  • ??

Такие последовательности могут содержать несколько суррогатных пар или даже неразрывные последовательности, не сводящиеся к простой проверке одной пары.

3. Непонимание уровня проверки

Проверка работает на уровне UTF-16, а не на уровне Unicode grapheme clusters.

Поведение при сериализации и API

При передаче данных через JSON:

  • строка уже сериализована в UTF-16/UTF-8 представление
  • возможна потеря информации при неправильной обработке промежуточных слоёв
  • валидация с @IsSurrogatePair выявляет повреждения до дальнейшей обработки

Внутренняя логика проверки

На уровне реализации подход сводится к проходу по строке и анализу кодовых единиц:

  • обнаружение диапазона high surrogate
  • проверка следующего элемента на low surrogate
  • подтверждение пары
  • отклонение одиночных значений

Такой подход позволяет выявлять неконсистентные UTF-16 последовательности, возникающие при обрезке строк или некорректной кодировке.

Практическое значение валидации

Использование декоратора повышает устойчивость систем к:

  • обрезанным строкам при лимитах базы данных
  • ошибкам при конвертации между UTF-8 и UTF-16
  • некорректным данным из внешних API
  • повреждённым текстовым потокам

Валидация на уровне суррогатных пар выступает как низкоуровневый контроль целостности строковых данных в экосистеме TypeScript и JavaScript.