RawObjectDataProcessor
— Теоретический минимум
При работе в RawObjectDataProcessor необходимо учитывать некоторые нюансы JavaScript/TypeScript, а вернее, стандарта ECMAScript и связанных с ним. Такие нюансы, разумеется, документированы в официальных и неофициальных источниках, однако начинающие программисты которых немало их часто не знают.
Классификация объектных данных
Как видно из названия «RawObjectDataProcessor», этот утилитарный класс предназначен для работы с объектными данными, то есть данными, удовлетворяющими условию typeof N === "object", при этом не являющимися null
(а typeof null
тоже является "object"
с точки зрения стандарта ECMAScript). Однако, дочерние свойства могут иметь и другие типы: Number
, String
, Boolean
, Тем не менее, на данный момент полностью поддерживаются только входные данные, совместимые с JSON. Соответствующий тип имеет следующее определение:
export type ParsedJSON = ParsedJSON_Object | ParsedJSON_Array;
export type ParsedJSON_Object = { [key: string]: ParsedJSON_NestedProperty; };
export type ParsedJSON_Array = Array<ParsedJSON_NestedProperty>;
export type ParsedJSON_NestedProperty =
number |
string |
boolean |
null |
ParsedJSON_Object |
ParsedJSON_Array |
undefined;
Хотя такие термины, как «объекты» и «массивы» часто применяются по отношению к JSON, необходимо понимать, что JSON — это строка, а не объект. Именно поэтому приведённые выше имена алиасов начинаются с «parsed», а не просто JSON_Object
или JSON_Array
.
В случае ECMAScript-языков для того, чтобы получить из JSON-строки обычный объект, чаще всего используется JSON.parse()
. Но хотя формат JSON и произошёл от JavaScript (что понятно из расшифровки — «JavaScript Object Notation»), с этим форматом данных работают и большинство других популярных языков программирования, но там JSON-строка преобразуется в типы данных, поддерживаемые соответствующим языком программирования.
В будущем возможно будет добавлена поддержка и других типов дочерних свойств, например Date или BigInt. Но на данный момент эта функциональность востребована мало, так как обычно при чтении данных из внешних источников и при образовании их в JavaScript-объект они совместимы с JSON, а если нужны свойства типа Date
или BigInt
, то их можно преобразовать из строк в ходе постобработки.
Итак, RawObjectDataProcessor работает со следующими четырьмя подтипами объектов:
↓ Тип ключей / ➝ Число элементов | Фиксированное | Произвольное |
---|---|---|
Строковые | Объект фиксированной структуры | Ассоциативный массив |
Неотрицательные целочисленные | Кортеж | Индексный массив |
Объекты фиксированной структуры
Для этих подтипов объектов подразумевается, что имена всех возможных свойств известны заранее. Тип SampleType
из демо является примером такого подтипа объектов:
type SampleType = {
foo: number;
bar: string;
baz: boolean;
hoge?: number;
fuga: string | null;
quux: {
alpha: number;
bravo: "PLATINUM" | "GOLD" | "SILVER";
};
};
Фиксированная структура не является запретом на полиморфные свойства, однако возможные варианты должны быть заранее известны, как в примере ниже.
type SampleTypeWithPolymopthocProperties = {
foo:
{ hoge: string; } |
{ fuga: number: }
bar: number:
baz: string;
};
Такой тип объектов является наиболее популярным, потому что чаще всего именно такое данные в сериализованном виде данные отправляются с клиента на сервер и наоборот. Такой же тип данный обычно представляют собой настройки, хранящиеся в файлах наподобие JSON, YAML и так далее (например, конфигурация TypeScript — обычно tsconfig.json).
Объекты типа «ассоциативный массив»
Ассоциативные массивы в отличие от объектов фиксированной структуры могут иметь произвольное количество свойств, имена которых заранее не
известны.
До стандарта ESMAScript 2015 в качестве таких массивов использовались обычные объекты, с точки зрения выходного JavaScript-кода ничем не отличающиеся от объектов фиксированной структуры. В TypeScript подобные ассоциативные массивы аннотируются одним из следующих способов:
type AssociativeArray<ValueType> = { [ key: string ]: ValueType };
// Или
type AssociativeArray<ValueType> = Record<string, ValueType>;
Введённый в стандарте ESMAScript 2015 тип данных Map можно или не нельзя называть ассоциативным массивом в зависимости от того, допускать ли у такого типа массивов ключи произвольного типа (в частности, Map не запрещает объектные ключи). Так или иначе, тип данных Map несовместим с JSON, однако такой совместимости можно достичь, преобразовав Map в двумерный индексный массив, где в каждом дочернем массиве первый элемент — ключ, а второй — значение:
const sampleMap: Map<string, number> = new Map([
[ "alpha", 1 ],
[ "bravo", 2 ],
[ "charlie", 3 ]
]);
const serializedMap: string = JSON.stringify(Array.from(sampleMap.entries()));
Другими примерами часто используемых ассоциативных массивов являются многие поля package.json — scripts
, dependencies
, devDependencies
, peerDependencies
и так далее.
RawObjectDataProcessor, разумеется, работает с обычными ассоциативными массивами, которые представляют собой объекты со строковыми ключами, однако не поддерживает входные даынне типа Map ввиду их несовместимости с JSON. Тем не менее, с помощью функциональности постобработки можно преобразовать желаемые данные и к Map.
Индексные массивы
Набор данных, расположенных в памяти непосредственно друг за другом, доступ к которым осуществляется по индексам — целым числам начиная с 0
, Как в русском, так и в английском языках так сложилось, что просто «массивами» (array(s)) часто называют именно индексные, а не ассоциативные массивы.
Во многих жёстко типизированных языках программирования (в частности, C++, C# и Java) обычные индексные массивы имеют фиксированное количество элементов и часто единый тип элементов. Если требуется добавление и/или удаление элементов, то используются специализированные коллекции — с точки зрения концепции те же самые индексные массивы, но обеспечивающие возможность добавления и/или удаления элементов за счёт замены одного стандартного массива на другой внутри себя.
В же отношении ECMAScript, индексный массив — экземпляр класснопободного объекта Array (утверждение X instanceof Array истинно). Поскольку такими индексными массивами можно гибко манипулировать (в частности, указывать элементы совершенно разных типов, добавлять элементы и удалять их), то нужды в дополнительных коллекциях пока что нет.
Аннотировать индексный массив в TypeScript можно одним из следующих способов:
const indexedArray: string[] = [ "Alpha", "Bravo" ];
// Или
const indexedArray: Array<string> = [ "Alpha", "Bravo" ];
Как и объекты фиксированной структуры, индексные массивы являются одним из самых популярных типов данных, потому широко используется всюду. В отличие объектов фиксированной структуры и ассоциативных массивов, индексные массивы гарантируют порядок элементов, что и делает их очень ценными.
Кортежи
С точки зрения концепции, кортежи — массивы (вообще-то говоря, не только индексные) фиксированного количества элементов.
До стандарта ECMAScript 2025 (не путать с упомянутым выше ECMAScript 2015), в JavaScript не было специализированного инструмента для создания массивов ограниченной длины. В TypeScript термин «кортеж» («tuple») применялся только к индексным массивам, аннотируемым следующим образом:
const tupleOfTwoElements: [ string, number ] = [ "ALPHA", 1 ];
const tupleOfThreeElements: [ string, number, boolean ] = [ "BRAVO", 2, true ];
В стандарте ECMAScript 2025 появился новый тип данных Tuple который помимо того, что должен иметь фиксированное количество элементов, также после инициализации является доступным только доступен для чтения, а потому для гарантии неизменяемости не может содержать в себе объектов никаких подтипов, будь то Array, Map, Set или любой другой объект. Этот тип данных слишком молодой, потому пройдёт некоторое время, прежде чем он станет широко поддерживаться в различных средах выполнения. Также можно предположить, что из-за своих ограничений он не получит широкого распространения, и естественно, RawObjectDataProcessor такой тип данных не поддерживает. Ввиду этого, далее речь будет идти о TypeScript-кортежах, которые уже совместимы с RawObjectDataProcessor.
Кортежи не очень популярны, однако примеры использования имеются. Так, функция useState
из React возвращает кортеж из двух элементов. С натяжкой можно назвать «кортежами» конфигурацию многих (но не всех) ESLint-правил, в которых второй элемент опциональный. Тем не менее, на каждой из двух позиций ожидается элемент конкретного типа, что является основным признаком кортежей.