forked from greg9504/ts-json-serializer
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Christoph Bühler
committed
Feb 13, 2017
1 parent
80bb37b
commit d01f14e
Showing
17 changed files
with
3,075 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 . |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) { } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export type TransportObject = { | ||
__type: string; | ||
__value: any; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.