Проверка JSON при помощи декораторов в TypeScript

от автора

Я очень люблю статические типы, поэтому 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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *