Skip to content

Commit

Permalink
adding first version of the lib
Browse files Browse the repository at this point in the history
  • Loading branch information
Christoph Bühler committed Feb 13, 2017
1 parent 80bb37b commit d01f14e
Show file tree
Hide file tree
Showing 17 changed files with 3,075 additions and 10 deletions.
30 changes: 30 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Logs
logs
*.log
npm-debug.log*

# Dependency directory
node_modules

# Optional npm cache directory
.npm

# Optional REPL history
.node_repl_history

# Typescript stuff
typings
build
docs
coverage
test
tslint.json
tsconfig

*.ts
!*.d.ts
*.js.map
*.spec.js
*.spec.ts
*.spec.d.ts
*.spec.js.map
12 changes: 12 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
language: node_js
node_js:
- '6'
- '7'

script: npm test

after_script: npm install coveralls@^2.11.9 && cat ./coverage/lcov-mapped.info | coveralls

# before_deploy:
# - npm run bootstrap
# - tsc --outDir .
73 changes: 73 additions & 0 deletions Resolver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { DuplicateTypeRegistration } from './errors';
import { SerializableType } from './SerializableType';

/**
* TODO
*
* @export
* @class Resolver
*/
export class Resolver {
private static _instance: Resolver;

public static get instance(): Resolver {
if (!Resolver._instance) {
Resolver._instance = new Resolver();
}
return Resolver._instance;
}

private types: { [name: string]: SerializableType } = {};

private constructor() { }

/**
* TODO
*
* @memberOf Resolver
*/
public reset(): void {
this.types = {};
}

/**
* TODO
*
* @param {SerializableType} model
*
* @memberOf Resolver
*/
public addType(type: SerializableType): void {
if (this.types[type.name]) {
throw new DuplicateTypeRegistration(type.name);
}

this.types[type.name] = type;
}

/**
* TODO
*
* @param {string} name
* @returns {(SerializableType | undefined)}
*
* @memberOf Resolver
*/
public getType(name: string): SerializableType | undefined {
return this.types[name];
}

/**
*
*
* @param {*} obj
* @returns {(SerializableType | undefined)}
*
* @memberOf Resolver
*/
public getTypeByObject(obj: any): SerializableType | undefined {
return Object.keys(this.types)
.map(key => this.types[key])
.find(o => obj.constructor === o.ctor);
}
}
35 changes: 35 additions & 0 deletions Serializable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { SerializableType } from './SerializableType';
import { Resolver } from './Resolver';
import { TransportObject } from './TransportObject';
import { NoFactoryProvidedError, NoNameProvided } from './errors';

/**
*
*
* @export
* @interface SerializableOptions
*/
export interface SerializableOptions {
name?: string;
factory?: <T>(json: TransportObject) => T;
};

/**
* TODO
*
* @export
* @param {SerializableOptions} [options]
* @returns {ClassDecorator}
*/
export function Serializable(options?: SerializableOptions): ClassDecorator {
return (type: Function) => {
const name = options && options.name ? options.name : (type as any).name;
if (type.length > 0 && (!options || !options.factory)) {
throw new NoFactoryProvidedError(name);
}
if (!name) {
throw new NoNameProvided();
}
Resolver.instance.addType(new SerializableType(name, type, options ? options.factory : undefined));
};
}
9 changes: 9 additions & 0 deletions SerializableType.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* TODO
*
* @export
* @class SerializableType
*/
export class SerializableType {
constructor(public name: string, public ctor: Function, public factory?: <T>(json: any) => T) { }
}
4 changes: 4 additions & 0 deletions TransportObject.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export type TransportObject = {
__type: string;
__value: any;
};
202 changes: 202 additions & 0 deletions TsSerializer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
import { ReferenceObjectNotFoundError, TypeNotRegisteredError } from './errors';
import { Resolver } from './Resolver';
import { TransportObject } from './TransportObject';

type RerializationReferences = {
[type: string]: any[];
};

/**
*
*
* @class ReferencedObject
*/
class ReferencedObject {
public type: string;
public index: number;

constructor(referenceInfo: { type: string, index: number }) {
this.type = referenceInfo.type;
this.index = referenceInfo.index;
}
}

/**
* TODO
*
* @export
* @class TsSerializer
*/
export class TsSerializer {
private references: RerializationReferences;

/**
* TODO
*
* @readonly
* @type {Resolver}
* @memberOf TsSerializer
*/
public get resolver(): Resolver {
return Resolver.instance;
}

/**
*
*
* @param {*} objectOrArray
* @returns {string}
*
* @memberOf TsSerializer
*/
public serialize(objectOrArray: any): string {
this.references = {};
let serialized: any;
if (objectOrArray.constructor === Array) {
serialized = objectOrArray.map(o => this.serializeObject(o));
} else {
serialized = this.serializeObject(objectOrArray);
}
// this.resolveObjectReferences(serialized);
return JSON.stringify(serialized);
}

/**
*
*
* @template T
* @param {string} json
* @returns {T}
*
* @memberOf TsSerializer
*/
public deserialize<T>(json: string): T {
this.references = {};
const parsed = JSON.parse(json);
let deserialized: any;
if (parsed.constructor === Array) {
deserialized = parsed.map(o => this.deserializeObject(o));
} else {
deserialized = this.deserializeObject(parsed);
}
this.resolveReferences(deserialized);
return deserialized;
}

/**
*/
private serializeObject(obj: any): TransportObject {
if (obj.constructor === Date) {
return {
__type: 'Date',
__value: obj
};
} else if (typeof obj === 'object') {
const type = this.resolver.getTypeByObject(obj),
transformedObj: any = {};
if (!type) {
throw new TypeNotRegisteredError(obj);
}

if (!this.references[type.name]) {
this.references[type.name] = [];
}

const alreadyIndexed = this.references[type.name].find(o => o === obj);

if (alreadyIndexed) {
return {
__type: 'ref',
__value: {
type: type.name,
index: this.references[type.name].indexOf(obj)
}
};
} else {
this.references[type.name].push(obj);
}

for (let property of Object.keys(obj).filter(o => typeof obj[o] !== 'function')) {
transformedObj[property] = this.serializeObject(obj[property]);
}
return {
__type: type.name,
__value: transformedObj
};
} else {
return {
__type: obj.constructor.name,
__value: obj
};
}
}

/**
*
*
* @private
* @param {TransportObject} obj
* @returns {*}
*
* @memberOf TsSerializer
*/
private deserializeObject(obj: TransportObject): any {
switch (obj.__type) {
case 'Date':
return new Date(obj.__value);
case 'Number':
return Number(obj.__value);
case 'String':
return String(obj.__value);
case 'Boolean':
return Boolean(obj.__value);
case 'ref':
return new ReferencedObject(obj.__value);
default:
const type = this.resolver.getType(obj.__type),
transformedObj: any = {};

if (!type) {
throw new TypeNotRegisteredError(obj);
}

for (let property of Object.keys(obj.__value)) {
transformedObj[property] = this.deserializeObject(obj.__value[property]);
}

const createdObj = type.factory ?
type.factory(transformedObj) :
Object.assign(new (type as any).ctor(), transformedObj);

if (!this.references[type.name]) {
this.references[type.name] = [];
}
this.references[type.name].push(createdObj);

return createdObj;
}
}

/**
*
*
* @private
* @param {*} obj
* @returns {void}
*
* @memberOf TsSerializer
*/
private resolveReferences(obj: any): void {
for (let property of Object.keys(obj)) {
const prop = obj[property];
if (prop instanceof ReferencedObject) {
if (!this.references[prop.type]) {
throw new ReferenceObjectNotFoundError();
}
obj[property] = this.references[prop.type][prop.index];
} else if (typeof prop === 'object') {
this.resolveReferences(prop);
}
}
}
}
Loading

0 comments on commit d01f14e

Please sign in to comment.