@civ-clone/core-data-object
Version:
A data exchange object for converting a subset of a class instances to plain JSON.
178 lines (146 loc) • 4.17 kB
text/typescript
import {
AdditionalDataRegistry,
instance as additionalDataRegistryInstance,
} from './AdditionalDataRegistry';
import AdditionalData from './AdditionalData';
import EntityRegistry from '@civ-clone/core-registry/EntityRegistry';
import { IConstructor } from '@civ-clone/core-registry/Registry';
import generateInheritance from './lib/generateInheritance';
export type DataObjectFilter = (object: DataObject) => any;
export type PlainObject = {
[key: string | symbol | number]: any;
};
export type ObjectStore = {
[key: string]: PlainObject;
};
export type ObjectMap = {
hierarchy: PlainObject;
objects: ObjectStore;
};
export interface IDataObject {
addKey(...keys: (string | number | symbol)[]): void;
id(): string;
keys(): (string | number | symbol)[];
sourceClass(): IConstructor<this>;
toPlainObject(): PlainObject;
}
const idCache: { [key: string]: number | bigint } = {},
idProvider = (object: DataObject) => {
const className = object.sourceClass().name,
current = idCache[className];
if (!current) {
idCache[className] = 0;
}
if (current >= Number.MAX_SAFE_INTEGER) {
idCache[className] = BigInt(current);
}
return className + '-' + (++idCache[className]).toString(36);
},
toPlainObject = (
value: any,
objects: ObjectStore,
filter: (object: any) => any = (object) => object,
additionalDataRegistry: AdditionalDataRegistry = additionalDataRegistryInstance
): PlainObject | PlainObject[] => {
value = filter(value);
if (value instanceof EntityRegistry) {
value = value.entries();
}
if (Array.isArray(value)) {
return value.map(
(item: any): PlainObject =>
toPlainObject(item, objects, filter, additionalDataRegistry)
);
}
if (value instanceof DataObject) {
const id = value.id();
if (!(id in objects)) {
const plainObject: PlainObject = {
_: value.sourceClass().name,
__: generateInheritance(value),
};
objects[id] = plainObject;
value.keys().forEach((key: string): void => {
const keyValue: any =
value[key] instanceof Function
? (value[key] as unknown as Function)()
: value[key];
plainObject[key] = toPlainObject(
keyValue,
objects,
filter,
additionalDataRegistry
);
});
additionalDataRegistry
.getByType(value.sourceClass())
.forEach((additionalData: AdditionalData): void => {
plainObject[additionalData.key()] = toPlainObject(
additionalData.data(value),
objects,
filter,
additionalDataRegistry
);
});
}
return {
'#ref': id,
};
}
if (value instanceof Function) {
return {
_: value.name,
__: generateInheritance(value),
};
}
if (value && value instanceof Object) {
return Object.entries(value).reduce(
(object: PlainObject, [key, value]) => {
object[key] = toPlainObject(
value,
objects,
filter,
additionalDataRegistry
);
return object;
},
{}
);
}
return value;
};
export class DataObject implements IDataObject {
#id: string;
#keys: (keyof this)[] = ['id'];
constructor() {
this.#id = idProvider(this);
}
addKey(...keys: (keyof this)[]): void {
this.#keys.push(...keys);
}
id(): string {
return this.#id;
}
keys(): (keyof this)[] {
return this.#keys;
}
sourceClass<T extends NewableFunction>(): T {
return this.constructor as T;
}
toPlainObject(
dataObjectFilter: DataObjectFilter = (object) => object,
additionalDataRegistry: AdditionalDataRegistry = additionalDataRegistryInstance
): ObjectMap {
const objects = {};
return {
hierarchy: toPlainObject(
this,
objects,
dataObjectFilter,
additionalDataRegistry
),
objects,
};
}
}
export default DataObject;