Unicode в JavaScript основан на UTF-16, где символы вне базовой многоязычной плоскости (BMP) кодируются парой 16-битных значений — суррогатной парой. Это напрямую влияет на работу со строками, содержащими эмодзи, редкие иероглифы, математические символы и исторические письменности.
В UTF-16 каждый символ может занимать 1 или 2 16-битных элемента:
BMP (U+0000 — U+FFFF) — представляется одним 16-битным словом
Дополнительные плоскости (U+10000 — U+10FFFF) — кодируются двумя 16-битными значениями:
Такая пара называется суррогатной парой.
Пример эмодзи ?:
0xD83D 0xDE04 (суррогатная пара)В строках JavaScript это важно, потому что length,
индексация и многие операции работают с 16-битными кодовыми единицами, а
не с полноценными Unicode-символами.
В библиотеке валидации class-validator декоратор
@IsSurrogatePair предназначен для проверки строк,
содержащих корректные суррогатные пары UTF-16.
Основная цель проверки — убедиться, что строка включает символы, представленные именно через суррогатные пары, и не содержит повреждённых или разорванных кодовых единиц.
Декоратор применяется к строковым свойствам класса и участвует в процессе валидации DTO или моделей данных.
@IsSurrogatePair выполняет анализ строки на уровне
UTF-16 последовательности:
Ключевое свойство проверки:
строка должна содержать валидные пары, а не разорванные суррогаты
Некорректные случаи:
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-данных:
JavaScript строки не хранят символы как Unicode-скаляры, а используют UTF-16 кодовые единицы. Это приводит к ряду особенностей:
'?'.length // 2
Хотя визуально это один символ, фактически это две кодовые единицы.
@IsSurrogatePair учитывает именно этот уровень
представления, а не абстрактные Unicode-кодпоинты.
Суррогатная пара — это не просто “эмодзи” или “не-BMP символ”. Проверка фокусируется на корректности UTF-16 представления, а не на семантике символа.
Возможные различия:
@IsSurrogatePair часто комбинируется с другими
проверками строк:
import { IsString, IsNotEmpty, IsSurrogatePair } from 'class-validator';
class ReactionDto {
@IsString()
@IsNotEmpty()
@IsSurrogatePair()
reaction: string;
}
Такая комбинация задаёт строгие требования:
Суррогатная пара не эквивалентна “наличию эмодзи”. Некоторые эмодзи могут быть составными последовательностями (ZWJ-sequences), которые не всегда сводятся к одной суррогатной паре.
Некоторые символы представляют собой комбинации нескольких Unicode-точек:
Такие последовательности могут содержать несколько суррогатных пар или даже неразрывные последовательности, не сводящиеся к простой проверке одной пары.
Проверка работает на уровне UTF-16, а не на уровне Unicode grapheme clusters.
При передаче данных через JSON:
@IsSurrogatePair выявляет повреждения до
дальнейшей обработкиНа уровне реализации подход сводится к проходу по строке и анализу кодовых единиц:
Такой подход позволяет выявлять неконсистентные UTF-16 последовательности, возникающие при обрезке строк или некорректной кодировке.
Использование декоратора повышает устойчивость систем к:
Валидация на уровне суррогатных пар выступает как низкоуровневый контроль целостности строковых данных в экосистеме TypeScript и JavaScript.