<!DOCTYPE html>
<html>
<head>
<title>Конвертер на декораторах TypeScript</title>
</head>
<body>
<h3>Input JSON:</h3>
<pre id="jsonInput"></pre>
<h3>Parse result:</h3>
<pre id="parseResult"></pre>
<h3>Serialize result:</h3>
<pre id="serializeResult"></pre>
</body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/reflect-metadata/0.1.8/Reflect.min.js"></script>
<script src="Script.js"></script>
<script>
var jsonData =
`{
"username": "PFight77",
"email": "test@gmail.com",
"doc": {
"info": "Author of the article"
}
}`;
document.getElementById("jsonInput").innerText = jsonData;
// Test parser
var user = UserInfo.parse(jsonData);
document.getElementById("parseResult").innerText = JSON.stringify(user, null, 4);
var serializedJSON = user.serialize();
document.getElementById("serializeResult").innerText = serializedJSON;
</script>
</html>
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"removeComments": false,
"noImplicitAny": true,
"suppressImplicitAnyIndexErrors": true
}
}
// Объявляем уникальные ключи, по которым будем идентифицировать наши метаданные
const ServerNameMetadataKey = "Habrahabr_PFight77_ServerName";
const AvailableFieldsMetadataKey = "Habrahabr_PFight77_AvailableFields";
// Объявляем декоратор
function ServerModelField(name?: string) {
return (target: Object, propertyKey: string) => {
// Сохраняем в метаданных переднный name, либо название самого свойства, если параметр не задан
Reflect.defineMetadata(ServerNameMetadataKey, name || propertyKey, target, propertyKey);
// Проверяем, не определены ли уже availableFields другим экземпляром декоратора
var availableFields = Reflect.getMetadata(AvailableFieldsMetadataKey, target);
if (!availableFields) {
// Ok, мы первые, значит создаем новый массив
availableFields = [];
// Не передаем 4-й параметр(propertyKey) в defineMetadata,
// т.к. метаданные общие для всех полей
Reflect.defineMetadata(AvailableFieldsMetadataKey, availableFields, target);
}
// Регистрируем текущее поле в метаданных
availableFields.push(propertyKey);
}
}
function convertFromServer<T>(serverObj: Object, type: { new(): T ;} ): T {
// Создаем объект, с помощью конструктора, переданного в параметре type
var clientObj: T = new type();
// Получаем контейнер с метаданными
var target = Object.getPrototypeOf(clientObj);
// Получаем из метаданных, какие декорированные свойства есть в классе
var availableNames = Reflect.getMetadata(AvailableFieldsMetadataKey, target) as [string];
if (availableNames) {
// Обрабатываем каждое свойство
availableNames.forEach(propName => {
// Получаем из метаданных имя свойства в JSON
var serverName = Reflect.getMetadata(ServerNameMetadataKey, target, propName);
if (serverName) {
// Получаем значение, переданное сервером
var serverVal = serverObj[serverName];
if (serverVal) {
var clientVal = null;
// Проверяем, используются ли в классе свойства декораторы @ServerModelField
// Получаем конструктор класса
var propType = Reflect.getMetadata("design:type", target, propName);
// Смотрим, есть ли в метаданных класса информация о свойствах
var propTypeServerFields = Reflect.getMetadata(AvailableFieldsMetadataKey, propType.prototype) as [string];
if (propTypeServerFields) {
// Да, класс использует наш декоратор, обрабатываем свойство рекурсивно
clientVal = convertFromServer(serverVal, propType);
} else {
// Нет, просто копируем значение
clientVal = serverVal;
}
// Записываем результат в конечный объект
clientObj[propName] = clientVal;
}
}
});
} else {
errorNoPropertiesFound(getTypeName(type));
}
return clientObj;
}
function convertToServer<T>(clientObj: T): Object {
var serverObj = {};
var target = Object.getPrototypeOf(clientObj);
var availableNames = Reflect.getMetadata(AvailableFieldsMetadataKey, target) as [string];
availableNames.forEach(propName=> {
var serverName = Reflect.getMetadata(ServerNameMetadataKey, target, propName);
if (serverName) {
var clientVal = clientObj[propName];
if (clientVal) {
var serverVal = null;
var propType = Reflect.getMetadata("design:type", target, propName);
var propTypeServerFields = Reflect.getMetadata(AvailableFieldsMetadataKey, propType.prototype) as [string];
if (clientVal && propTypeServerFields) {
serverVal = convertToServer(clientVal);
} else {
serverVal = clientVal;
}
serverObj[serverName] = serverVal;
}
}
});
if (!availableNames) {
errorNoPropertiesFound(parseTypeName(clientObj.constructor.toString()));
}
return serverObj;
}
class UserAdditionalInfo {
@ServerModelField("info")
public mRole: string;
}
class UserInfo {
@ServerModelField("username")
private mUserName: string;
@ServerModelField("email")
private mEmail: string;
@ServerModelField("doc")
private mAdditionalInfo: UserAdditionalInfo;
public get DisplayName() {
return mUserName + " " + mAdditionalInfo.mRole;
}
public get ID() {
return mEmail;
}
public static parse(jsonData: string): UserInfo {
return convertFromServer(JSON.parse(jsonData), UserInfo);
}
public serialize(): string {
var serverData = convertToServer(this);
return JSON.stringify(serverData, null, 4);
}
}