@itgenio/edik-reflection
v0.0.31
Published
Рефлексия
Downloads
141
Readme
Reflection
- зависимость
nanoid
для генерации ref id
Общее
Type.$$gemeta
Все данные об объекте хранятся в его типе в поле $$gemeta
. Удобно хранить связанные с типом мета-данные в его прототипе.
Дальнейшим развитием может быть сохранение не по строчному ключу, а символьному (Symbol, так делает Mobx)
Какие мета-данные есть у типа?
name:string
используется для поиска типа в качестве уникального идентификатора.
Можно было бы использовать имя конструктора, но после минификации оно становится не пригодным для использования.
displayName?:string
для красивостиisComponent?:boolean
компонент или нет (доп. атрибут для инспектора)fields?:{ name:string }[]
поляinline?:boolean
нужно ли инлайнить,т.е. вставлять объекты такого типа по месту не вынося в map.
Для типа SerializedRootObject
есть поддержка версионирования. (текущая версия схемы будет лежать в поле __meta.v
)
Про обычные и inline-типы
Библиотека позволяет сериализовывать циклические зависимости, а так же множественные ссылки на один объект и т.д.
Это работает благодаря тому, что все обработанные объекты помещяются в отдельный список. Перед тем, как обработать следующий объект, вначале проверяется, что он еще не обработан(т.е. его нет в списке).
Если объект есть в списке - значит мы уже обработали такой объект и можно идти дальше.
При этом, в поле будет записан идентификатор ссылки на этот объект. Т.е. сколько бы полей не ссылалось на данный объект, все они будут ссылаться на один конкретный экземпляр.
Важно: при копировании полей объекта такие ссылки на объекты не будут копироваться.
Пример:
class SomeComponent extends CBaseComponent {
field: CBaseComponent;// предположим наш компонент ссылается на какой-то компонент.
}
const comp:SomeComponent = /*...*/;
const copy = comp.clone(); //копируем
assert(comp.field === copy.field);//!!! хотя сам компонент скопировался, ссылка при этом указывает на старый объект!
Такие объекты имеют Обычный тип.
В некоторых случаях хочется, чтобы объект не выносился в такой список и не мог иметь ссылок на себя кроме одной.
Для этого нужны inline-типы.
Самый простой пример - тип Color
. Мы не хотим чтобы на него ссылались разные объекты, при этом при копировании создается новый экземпляр данного типа.
Пример:
class SomeComponent extends CBaseComponent {
color: Color; //Color - inline-тип
}
const some:SomeComponent = ...;
const copy = some.copy(); //копируем
assert(some.color !== copy.color);//ссылки разные, т.к. цвет скопировался!
TODO
- сократить размер?
- если в массиве(+ set и map) все элементы - ссылки, то можно применить специальный флаг.(т.к. в большинстве случаев массивы однотипны)
Процесс регистрации
- берем тип
- регистрируем его поля в мету
- далее идет по иерархии ниже и регистрируем следующий тип, если нужно.
- так же регистрируем в общей хранилище
Object.$$gemetamap : Map<string, Type>
чтобы в дальнейшем из любого места могли получить тип по имени.
Если на верхнем уровне есть поле с именем, как и на нижних уровнях, то будет браться поле из верхнего уровня.
class CBase {
id: string; //для CBase регаем id=>string
}
class CSome extends CBase {
id: number; //для CSome регаем id=>number;
}
//getField(CSome,'id') => number;
//getField(CBase,'id') => string;
Early(ранняя) регистрация
Возможна такая ситуация, когда базовый класс не успел зарегистрироваться, но уже регистрируется наследник.
В таком случае наступает ранняя регистрация для базового типа. Это значит:
- мы не знаем точно про доп. атрибуты и имя
- мы знаем только о полях
Поэтому, для базового типа регистрируются поля и ставиться флаг, что была ранняя регистрация.
Сериализация
Поддержка и фичи
Типы:
type Primitives = string | number | boolean
Array<any>
Set<any>
Map<string, any>
Есть таблица
type ObjId = string;
type ObjData = any;
const objects = new Map<ObjId, ObjData>();
Все объекты(за исключением inline
-типов) сериализуются в таблицу. Ссылки на такие объекты сериализуются как строки-указатели на ID объекта из таблицы.
Сериализация
У Serialized***
есть поле __meta.map
хранящее все объекты, на которые могут ссылаться внутренние объекты.
Десериализация
Для объектов:
- ID
- тип
- остальные данные
Тип при этом должен быть зареган в хранилище типов либо указан явно при десериализации.
Десериализация происходит в два этапа.
Первый этап
На первом этапе создаются экземпляры объектов. При этом берется тип из сериализованного объекта.
Второй этап
На втором этапе объекты связываются друг с другом
Объекты
Запоминаем ID и помечаем поле как ссылка на объект
Массивы
Запоминаем ID и помечаем, какие индексы как ссылки
Что не работает (так задумано)
Ссылки на общий raw object, raw array
import { Serializer } from './serializer';
const s1 = new Type();
const s2 = new Type();
const sharedRawObj = { a: '1' };
s1.raw = s2.raw = sharedRawObj; //вот так не работает!
const obj = { s1, s2 };
const dobj = Serializer.serialize(Serializer.deserialize(obj));
//после десериализации, dobj.s1.raw !== dobj.s2.raw, ссылка не сохранилась
PoC / Ideas
External links
Иногда требуется сериализовать и десериализовать объект, но при этом, часть объектов, на которые ссылаемся, не находятся в иерархии и хочется сохранить ссылку на них после десериализации.
@gtype({ name: 'SomeType' })
class SomeType {
@gserializable()
id: string = 'randomId';
@gserializable()
externalLink?: SomeType;
}
const exernalObj = new SomeType();
const obj = new SomeType();
obj.externalLink = exernalObj;
Как сейчас:
const dobj = Serializer.deserialize(Serializer.serialize(obj));
assert(dobj.externalLink !== exernalObj); // сейчас вот так!
Предложение:
//serialize
const externalLinks = [externalObj];
const serialized = Serializer.serialize(obj, { externalLinks });
// deserialize
const dobj = Serializer.deserialize(serialized, { externalLinks: allObjsAndComps });
assert(dobj.externalLink === exernalObj);
interface ILinkable {
id: string;
}
Подробнее:
Когда встречаем объект, который находится в externalLinks
, подменяем тип на новый тип ExternalLink
:
class ExternalLink {
id: string;
}