Я очень люблю статические типы, поэтому TypeScript стал незаменимым помощником при работе с NodeJS или браузерным JS.
По долгу службы приходится иметь очень много дел с JSON и здесь система типов TypeScript не помогает ничем, даже мешает, ведь компилятор сообщает об отсутствии ошибок, JSON.parse возвращает тип Any. Кроме того, TypeScript не поддерживает рефлексию в виду специфики работы, а значит нет возможности проверить тип, основываясь на уже существующем коде. Также до последнего времени средств для мета-программирования не было вовсе.
Зачастую проверка корректности пришедшего JSON-объекта оборачивается громадным кодом в конструкторах классов, либо такими же конфигурационными файлами. Но, наконец-то, в TypeScript 1.5 появились декораторы.
Декораторы позволяют выполнить некие манипуляции с классом, методом, свойством или параметром во время их объявления, при этом возможна передача дополнительной информации о декорируемом объекте. Этим я и воспользовался.
Выглядит это так:
class X extends Model { @prop({ type: PropType.Array, arrayProp: { type: PropType.Number } }) a; } enum Enum1 { V1, V2, V3 } class MyClass extends Model { @prop({ type: PropType.Object, class: X }) prop1; @prop({ type: PropType.String }) propString; @prop({ type: PropType.Enum, class: Enum1 }) b: Enum1; } class TestString extends Model { @prop({ type: PropType.String }) prop: string; }
Получилось небольшое дублирование при указании типа, но это гораздо меньшее зло, чем описывать систему классов отдельно, чисто для проверки типов.
Задача
Нам необходимо получить из JSON-объекта полноценный TypeScript-объект без ошибок в полях, иерархической поддержкой вложенных объектов (со своими типами). Я не реализовывал работу с JSON, в корне которого не plain-object; не было необходимости.
Основные типы, которые я хотел получить на выходе из JSON:
- String
- Number
- Boolean
- Object
- Array
- Enum
- Any
Решение
Для реализации я создал декоратор свойства и специальный класс Model, который должны наследовать все классы, поля которых мы хотим проверять. При определении класса в специальное поле заносится информация обо всех полях, объявленных с декоратором (тип, обязательное ли и т.д.)
String — просто проверяется на собственно JS-объект String. Number и Boolean имеют возможность указать параметр isCasting, в таком случае проверяется, может ли значение быть приведенным к числу или возможно любое значение для Boolean.
Object — проверяется, является ли объект plain-object и если задан специальный параметр class, то создается его экземпляр, при этом если класс наследует Model, то соответственно проверятся и его типы.
Array — проверка, на JS-Array + возможность указать тип перечислямых значений. Каждое значение проверяется на тип отдельно.
Enum — если задан параметр class (в данном случае ссылка на enum), то проверяется существует ли в этом перечислении нужное значение. В JSON задается текстом (как в определении enum).
Код здесь я приводить не буду, он достаточно простой, выложил его на github. Вот пример использования для классов, описанных листингом выше:
var a = new MyClass({ propString: "test1", prop1: { a: [1, 2, 3] }, b: "V1" }); console.assert(a.prop1 instanceof X); console.assert(a.b === Enum1.V1, "Invalid JSON"); console.assert(a.propString === "test1"); try { new TestString({ prop: 123 }); console.assert(false, "Not check string field"); } catch (e) { } try { new MyClass({}); console.assert(false, "Not check required field"); } catch (e) { }
Как я уже сказал, это пока только прототип, в боевом режиме не участвовал, однако он дает почву для аналогичных решений или возможности использования декораторов.
P.S. Недавно на Хабре была опубликована статья о простоте Go — «Сложно о простоте Go». Так вот, новый ключевой символ @ в TypeScript уже позволил мне уменьшить количество кода в разы, а запомнил я его сразу же. А как я радовался появлению arrow-function! С нетерпением жду async/await (да, да 2 новых слова), которые позволят избавиться еще от тонны then, when, resolve, reject и т.д.
ссылка на оригинал статьи http://habrahabr.ru/post/262105/
Добавить комментарий